一. 链表的定义
链表是动态分配存储空间的链式存储结构。
其包括一个“头指针”变量,其中第0个结点称为整个链表的头结点,头结点中存放一个地址,该地址指向一个元素,头结点一般不存放具体数据,只是存放第一个结点的地址。
链表中每一个元素称为“结点”,每个结点都由两部分组成:存放数据元素的数据域和存储直接后继存储位置的指针域。指针域中存储的即是链表的下一个结点存储位置,是一个指针。多个结点链接成一个链表。
最后一个结点的指针域设置为空(NULL),作为链表的结束标志,表示它没有后继结点。
使用结构体变量作为链表中的结点,因为结构体变量成员可以是数值类型,字符类型,数组类型,也可以是指针类型,这样就可以使用指针类型成员来存放下一个结点的地址,使其它类型成员存放数据信息。
当一个序列中只含有指向它的后继结点的链接时,就称该链表为单链表。
单链表的示意图如下:
二. 单链表的表示及实现
链表和顺序表的最大不同之处就是链表的物理地址不是相邻的。顺序表中采用数组下标的形式(即物理地址的相邻)表示元素的逻辑顺序,而链表是采用指针的方式使不相邻的地址存储逻辑相邻的元素。
- 链表的优点:不需要连续的空间进行存储;对于插入、删除节点操作较为方便,时间复杂度O(1)。
- 链表的缺点:对于指定位置元素读取较顺序表麻烦;空间利用率比较低。
-
定义
#define ERROR -1 #define OK 0 typedef struct LNode{ int Data; struct LNode *Next; }List;
-
创建带头节点的单链表
List *CreatList(){ //创建带头节点的链表 List *L=NULL; L = (List *)malloc(sizeof(List)); if(!L) exit(-1);//申请存储失败 L->Data = -1; L->Next = NULL; return L; }
-
尾插法插入数据元素
int ListInsertR(List *L, int e){ //将数据e插入链表L尾部 List *p=L, *q=NULL;//p追踪链表尾部 q为新节点 q = (List *)malloc(sizeof(List)); if(!q) exit(-1);//存储申请失败 q->Data = e; q->Next = NULL; //查找链表尾部地址 while(p && p->Next){ p = p->Next; } q->Next = p->Next; p->Next = q; return OK; }
-
读取链表指定位置元素
int ListRead(List *L, int x, int *e){ //将链表中第 x个元素数据保存到e中 List *p=L; int i=0; while(i < x){ if(p->Next == NULL){ return ERROR;//读取元素越界 } p = p->Next; ++i; } (*e) = p->Data; return OK; }
-
获取链表长度
int GetListLength(List *L, int *e){ //将链表L的长度存储在e中(不含头结点) List *p=L->Next; int Length=0; //计算链表长度 while(p){ p = p->Next; ++Length; } //返回链表长度 (*e) = Length; return OK; }
-
从链表中删除指定位置节点
int ListDel(List *L, int x, int *e){ //在链表L中删除第x个节点并将数据存储在e中 List *q=L, *p=NULL;//q追踪x个节点 p保存节点释放 int i=0;//循环变量 if(x < 1) return ERROR;//删除节点非法 //找到前一个节点地址 while( i < x-1){ if(q->Next == NULL){ return ERROR;//删除元素越界 } q = q->Next; ++i; } //将节点删除 p = q->Next; q->Next = p->Next; (*e) = p->Data; free(p); return OK; }
-
函数验证
int GetListLength(List *L, int *e){ //将链表L的长度存储在e中(不含头结点) List *p=L->Next; int Length=0; //计算链表长度 while(p){ p = p->Next; ++Length; } //返回链表长度 (*e) = Length; return OK; } int main(int argc, char *argv[]) { List *L=NULL,*p=NULL; int i=1, e=0; //创建节点 L = CreatList(); printf("创建链表成功!\n"); GetListLength(L, &e); printf("当前链表长度为 %d 。\n",e); //循环插入元素 for(i;i<10;i++){ ListInsertR(L, i); } printf("向链表中插入了 %d 个数据元素。\n",i-1); //读取出链表内全部数据 p = L->Next; i=1; GetListLength(L, &e); printf("当前链表长度为 %d 。\n",e); printf("当前链表数据为:"); while(p){ ListRead(L, i, &e); printf("%d",e); ++i; p = p->Next; } i = 5; printf("删除第 %d 个节点。\n",i); ListDel(L, i, &e); p = L->Next; i=1; GetListLength(L, &e); printf("当前链表长度为 %d 。\n",e); printf("当前链表数据为:"); while(p){ ListRead(L, i, &e); printf("%d",e); ++i; p = p->Next; } return 0; }
经验证函数有效。