使用C语言实现线性表
线性表是最常用且最简单的一种数据结构。一个线性表是n个数据元素的有限序列,序列中的每个数据元素,可以是一个数字,可以是一个字符,也可以是复杂的结构体或对象。例如:1,2,3,4,5是一个线性表,A,B,C,D...Z是一个线性表,一列列车的车厢1,车厢2...车厢n是一个线性表。
线性表的机内表示法(又称存储结构)有2种,一种是顺序存储结构,另一种是链式存储结构。
顺序存储结构,顾名思义就是按顺序来存储的一种存储结构,比如线性表(1,2,3,4,5),共计5个元素,
每个int型的数据元素假设占用4个存储单元,假设第1个元素数字1的存储地址是1000,则第2个元素数字2的存储地址是1004,第3个元素数字3的存储地址是1008,依此类推,第n个数据元素的存储地址是LOC(an) = LOC(a1)+(n-1)k.(k表示每个数据元素占用的存储单元的长度)
显而易见,这种存储结构,相邻元素在物理位置上也相邻。
通常,我们把采用这种存储结构的线性表称为“顺序表”。
链式存储结构,它不要求相邻的元素在物理位置上也相邻,所以它可用一组处在任意位置的存储单元来存储线性表的数据元素。既然这样,那应该怎样表示数据元素之间的逻辑关系呢?
为了表示数据元素之间的逻辑关系,对于数据元素a1来说,除了存储其自身的信息之外,还需要存储一个指示其直接后继的信息,所以我们引入指针的概念:称存储数据元素信息的域称为数据域,而存储直接后继地址信息的域称为指针域。
我们形象地称这种即包含自身数据信息,又包含其直接后继地址信息的数据元素为“结点”。
显而易见,这种存储结构,相邻元素在物理位置上不一定相邻,他们之间用指针来表示逻辑关系。
通常,我们把采用这种存储结构的线性表称为“线性链表”。
有了基本的概念之后,我们就可以使用编程语言进行描述,使用C、C++、C#、Java等都可以,这篇文章我使用C语言描述。
一、顺序表
为了描述顺序表,我们首先要声明一个结构,如下:
1 2 3 4 5 6 7 8 | #define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量 #define LISTINCREMENT 10 //线性表存储空间的分配增量(当存储空间不够时要用到) typedef int ElemType; //数据元素的类型,假设是int型的 typedef struct { ElemType *elem; //存储空间的基地址 int length; //当前线性表的长度 int listsize; //当前分配的存储容量 }SqList; |
定义好线性表后,就可以对它进行操作了,常见的线性表的基本操作有:创建线性表、查找元素、插入元素、删除元素、清空、归并等。
线性表的基本操作在顺序表中的实现:
1.创建线性表
1 2 3 4 5 6 7 8 9 10 11 12 | int InitList(SqList &L) { L.elem = (ElemType *) malloc (LIST_INIT_SIZE * sizeof (ElemType)); //开辟一个存储空间,并把这块存储空间的基地址赋值给elem if (!L.elem) { return -1; //空间分配失败 } L.length = 0; //当前长度 L.listsize = LIST_INIT_SIZE; //当前分配量 return 0; } |
2.查找元素(按值查找)
线性表的按值查找是指在线性表中查找与给定值x相等的数据元素。完成该操作最简单的方法是从第一个元素a1开始依次和x比较,若相等,则返回该元素的下标。
若查遍整个表都没有找到与x相等的元素,则返回-1。
时间复杂度:算法的基本操作(比较x与L中第i<1,n>个元素)与元素x在表中的位置有关,也与表长有关。当a1=x时,比较1次成功,当an=x时,比较n次成功,平均比较次数为n+1/2,时间复杂度为O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 | int LocateElem(SqList L, ElemType x) { int pos = -1; for ( int i = 0; i < L.length; i++) { if (L.elem[i] == x) { pos = i; } } return pos; } |
3.插入元素
时间复杂度O(L.length)即O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | int ListInsert(SqList &L, int i, ElemType e) { //判断插入位置的合法性 if (i<1 || i>L.length+1) return -1; //判断存储空间是否够用 if (L.length >= L.listsize) { ElemType *newbase = (ElemType *) realloc (L.elem, (L.listsize + LISTINCREMENT)* sizeof (ElemType)); if (!newbase) return -1; //存储空间分配失败 L.elem = newbase; //新基址 L.listsize += LISTINCREMENT; //增加存储容量 } //插入操作 ElemType *q, *p; //定义2个指针变量 q = &(L.elem[i-1]); //q为插入位置(注意形参i是序号,序号是从从1开始的,而下标是从0开始的,因此这里转成下标后是i-1) for (p = &(L.elem[L.length - 1]); p >= q; --p) //从ai到an-1依次后移,注意后移操作要从后往前进行 { *(p + 1) = *p; } *q = e; ++L.length; //表长加1 return 0; } |
4.删除元素
时间复杂度O(L.length)即O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | int ListDelete(SqList &L, int i, ElemType &e) { //判断删除位置的合法性 if (i<1 || i>L.length) return -1; //删除操作 ElemType *q, *p; //定义2个指针变量 p = &(L.elem[i - 1]); //p为被删除元素的位置(注意形参i是序号,序号是从从1开始的,而下标是从0开始的,因此这里转成下标后是i-1) e = *p; //被删除的元素赋值给e(可能用不到,也可能用到,所以保存给e吧) q = L.elem + L.length - 1; //q指向表尾最后一个元素(q是最后一个元素的地址) for (++p; p <= q; ++p) //从p的下一个元素开始依次前移 { *(p - 1) = *p; } --L.length; //表长减1 return 0; } |
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | int main() { SqList list; InitList(list); int n = 10; //添加10个数字给线性表list for ( int i = 0; i < 10; i++) { ListInsert(list, i+1, i+1); } //删除第5个 ElemType e; ListDelete(list, 5, e); printf ( "删除的元素是:%d\n" , e); //在第2个位置插入一个元素-1 ListInsert(list, 2, -1); //输出线性表 for ( int i = 0; i < 10; i++) { printf ( "%d " , list.elem[i]); } //输出结果是:1 -1 2 3 4 6 7 8 9 10 system ( "pause" ); } |
二、线性链表
为了描述线性链表,我们首先要声明一个结构,如下:
1 2 3 4 | typedef struct LNode{ ElemType data; //数据域 struct LNode *next; //指针域 }LNode; |
定义好线性链表后,就可以对它进行操作了,常见的线性链表的基本操作有:查找、插入、删除、清空等。
线性表的基本操作在线性链表中的实现:
1.1创建链表(头插法) 时间复杂度O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | LNode * CreateListHead( int n) { LNode *L; L = (LNode *) malloc ( sizeof (LNode)); L->next = NULL; LNode *p; //p为新结点,指向最后一个元素 for ( int i = n; i > 0; --i) { p = (LNode *) malloc ( sizeof (LNode)); //为新结点开辟空间 scanf ( "%d" ,&p->data); p->next = L->next; L->next = p; } return L; } |
1.2创建链表(尾插法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | LNode * CreateListTail() { LNode *L; L = (LNode *) malloc ( sizeof (LNode)); L->next = NULL; //空表 LNode *s, *r = L; //r是尾指针 s是新结点 int x; scanf ( "%d" , &x); int flag = 0; //结束标记 while (x != flag) { s = (LNode *) malloc ( sizeof (LNode)); s->data = x; r->next = s; r = s; scanf ( "%d" , &x); } r->next = NULL; //最后一个元素next域指向NULL return L; } |
2.查找元素(取第i个元素)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int GetElem(LNode L, int i, ElemType &e) { LNode *head,*p; //head是头指针,p为查找下标 head = &L; p = head; //p的初值指向第1个元素 int j = 0; while (p !=NULL && j<i) { p = p->next; j++; } if (p == NULL || j>i) return -1; //第i个元素不存在 e = p->data; return 0; } |
3.插入元素
在链表中插入结点,只需要修改指针。
若要在第i个结点之前插入元素,则首先需要找到第i-1个结点,然后修改第i-1个结点的指针。
时间复杂度O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | int ListInsert(LNode *L, int i, ElemType e) { LNode *p; //设p为第i-1个结点 //首先要找到第i-1个结点 int j = 0; p = L; while (p != NULL && j < i-1) { p = p->next; j++; } //申请一个新结点 LNode *s = (LNode *) malloc ( sizeof (LNode)); s->data = e; s->next = p->next; //s的直接后继指向p原来的直接后继 p->next = s; //p的直接后继指向s return 0; } |
4.删除元素
若要删除第i个结点,则只需要修改第i-1个结点的指针指向第i+1个即可
时间复杂度O(L.length)即O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | int ListDelete(LNode *L, int i, ElemType &e) { LNode *p,*q; //设p为第i-1个结点 q标示删除位置 //首先要找到第i-1个结点 int j = 0; p = L; //p为L的基地址 while (p != NULL && j < i-1) { p = p->next; j++; } q = p->next; p->next = q->next; //p的next域指向p->next->next e = q->data; return 0; } |
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | int main() { LNode *L; L = CreateListTail(); //创建一个线性链表,输入0结束 ListInsert(L, 1, 100); //在第1个位置插入100 ElemType e; //待删除的元素 ListDelete(L, 3, e); //删除第3个元素 LNode *p; p = L; printf ( "输出线性链表:\n" ); while (p->next != NULL) { p = p->next; printf ( "%d " , p->data); } system ( "pause" ); } |
笔者初学数据结构,如有不足之处,欢迎指正。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构