小工具知识网

当前位置: 首页 > 行业知识 > 数据结构--线性表的链式表示[王道]

行业知识

数据结构--线性表的链式表示[王道]

2026-11-09 10:56:43 来源:互联网转载

���图来自王道课程,侵权立删。

本篇博客主要为个人学习记录。

目录

单链表的定义

代码定义-单链表

单链表的插入与删除

按位序插入(带头结点)

按位序插入(不带头结点)

指定结点的后插操作

指定节点的前插操作

按位序删除(带头结点)

扩展:指定结点*p的删除

单链表的查找

按位查找

按值查找

求链表长度

尾插法与头插法 (带头结点)

尾插法建立单链表

头插法建立单链表

双链表

双链表的初始化(带头结点)

双链表的插入

双链表的删除

双链表的遍历


单链表的定义

利用单链表可以解决顺序表需要 大量连续存储单元 的缺点,但附加的指针域,也存在浪费存储空间的缺点。单链表的元素离散的分布在存储空间中,因此是非随机存取的存储结构,即不能直接找到表中某个特定结点,查找特定结点时,需要从表头开始遍历,依次查找。

头结点 和 头指针的关系:不管带不带头结点,头指针始终指向链表的第一个结点, 头结点是带头结点的链表中的第一个结点,结点内通常不存储信息。

引入头结点后,有两个优点:

1.链表第一个位置上的操作和表中其他位置上的操作一致,无需进行特殊处理。

2.无论链表是否为空,其头指针都是指向头结点的非空指针(空表中 头结点的指针域为空),因此空表和非空表的处理得到了统一。

代码定义-单链表

声明一个单链表时,只需声明一个头指针L,指向单链表的第一个结点(带头结点)

#include//单链表的定义 typedef struct LNode{  //定义单链表结点类型 ElemType data;     //结点存放的数据域 struct LNode *next;//结点存放的指针域,指向下一个结点 }LNode, *LinkList;

需要注意

LinkList 强调这是一个单链表

LNode* 强调这是一个结点

不带头结点的单链表

#includetypedef struct LNode{ElemType data;      //数据域struct LNode *next; //指针域}LNode,*LinkList;//初始化空单链表(不带头结点)bool InitList(LinkList &L){L = NULL; //空表,暂时没有任何结点。防止脏数据return true; } //判断单链表是否为空(不带头结点)bool Empty(LinkList L){return (L == NULL);} int main(){LinkList L; //声明一个指向单链表的指针InitList(L);//初始化一个空表 return 0;}

带头结点的单链表 

#include#includetypedef struct LNode{Elemtype data;        //结点存放的数据域 struct LNode *next;   //结点存放的指针域 }LNode,*LinkList;//初始化空单链表(带头结点)bool InitList(LinkList &L){L = (LNode*)malloc(sizeof(LNode)); //分配空间给头节点if (L == NULL) return false; //分配失败L -> next = NULL; //头结点之后暂时无结点,指向NULLreturn true; } //判断单链表是否为空(带头结点)bool Empty(LinkList L){if (L->next == NULL) return true;else return false;} int main(){LinkList L; //声明一个指向单链表头结点的指针InitList(L);//初始化一个空表 return 0;}

单链表的插入与删除

按位序插入(带头结点)

ListInsert(&L,i,e):插入操作,在表L中的第i个位置上插入指定元素e。 找到第i-1个结点,将新结点插入其后。

头结点当作第0个结点。

#include#include//定义单链表 typedef struct LNode{int data;           //结点存放的数据域 struct LNode *next; //结点存放的指针域 }LNode, *LinkList;//在第i个位置插入元素e(带头结点)bool ListInsert(LinkList &L, int i, int e){if(i data = e;s -> next = p->next;p -> next = s;return true; } //防止程序无法运行 int main(){return 0;}

最好时间复杂度O(1) --结点直接插在头结点之后

最坏时间复杂度O(n) --结点插在链表末尾,需要循环多次

平均时间复杂度O(n)

按位序插入(不带头结点)

ListInsert(&L,i,e):插入操作,在表L中的第i个位置上插入指定元素e。 找到第i-1个结点,将新结点插入其后。   

由于不存在第0个结点(即头结点),因此i=1时需要做特殊处理。并且不带头结点的链表,进行插入、删除第一个元素时,需要更改头指针L的指向。(如图)

#include#include//定义单链表 typedef struct LNode{int data;           //结点存放的数据域 struct LNode *next; //结点存放的指针域 }LNode, *LinkList;//在第i个位置插入元素e(带头结点)bool ListInsert(LinkList &L, int i, int e){if(idata = e;s->next = L; L = s; //头指针指向新结点return true; } //其余结点,操作一致  逻辑与带头结点的链表一样 LNode *p; //指针p指向当前扫描到的结点int j = 1;//记录当前p指向的是第几个结点p = L ;   //L指向第一个结点 //循环,遍历链表,找到第i-1个结点 while(p != NULL && jnext; //指针向后一个结点移位j++;          } if(p == NULL)//i值不合法return false; LNode *s = (LNode *)malloc(sizeof(LNode)); //申请结点空间,s指向待插入的结点 s -> data = e;s -> next = p->next;p -> next = s;return true; //插入成功 } //防止程序无法运行 int main(){return 0;}

指定结点的后插操作

//后插操作:在p结点之后插入元素ebool InsertNextNode(LNode *p , int e){if(p == NULL) return false; LNode *s = (LNode *)malloc(sizeof(LNode)); //为结点s申请分配空间if(s == NULL) return false; //申请分配失败,比如内存不足s->data = e; //用结点s保存数据元素es->next = p->next;p->next = s; //将结点s连接到p之后return true; } 

时间复杂度O(1)

指定节点的前插操作

如何找到p结点的前驱节点? --1. 传入头指针    -- 2. 仍然使用后插法,交换数据域

第一种方法,时间复杂度为O(n),需要从头遍历链表,找到p结点的前驱结点,非常麻烦。

第二种方法,时间复杂度为O(1),代码如下:

//前插操作:在p结点之前插入元素ebool InsertPriorNode(LNode *p , int e){if(p == NULL) return false;LNode *s = (LNode *)malloc(sizeof(LNode)); //为新结点申请分配内存if(s == NULL) return false; //内存分配失败s -> next = p->next;p->next = s;       //新结点接到p结点之后 s->data = p->data; //将p中元素复制到s中 p->data = e;       //将e值覆盖到p结点上return true; }

王道书上的版本:

//前插操作 :王道书版本bool InsertPriorNode_ (LNode *p , LNode *s){if (p == NULL || s == NULL) return false;s ->next = p->next; //将p结点后的元素接到s后 p->next = s;        //将s连接到p后//交换数据域部分 int temp = p ->data;p->data = s->data;s->data = temp; return true;} 

按位序删除(带头结点)

ListDelete(&L,i,&e):删除操作,删除表L中第i个位置的元素,并用e返回删除元素的值。找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点。(如下图)

王道书上的描述也很好理解:假设结点*p为被删结点的前驱,仅需修改*p的指针域next,将*p的指针域next指向被删结点*q的下一结点,然后释放*q的存储空间。

//按位序删除结点(带头结点)bool ListDelete(LinkList &L, int i,int &e){if(inext == NULL) return false; //第i-1个结点之后无其他结点,错误LNode *q = p->next; //令q指向被删除结点e = q->data;        //e保存被删除的元素值,并返回p->next = q->next;  //将*q结点从链中断开free(q);            //释放结点q的存储空间return true;//删除成功 } 

最好时间复杂度--O(1)  删除头结点之后的第一个结点

最坏、平均时间复杂度--O(n)  删除最后的结点

扩展:指定结点*p的删除

删除结点*p,需要修改其前驱结点的next指针。 --方法1.传入头指针,循环遍历链表,寻找p的前驱结点   --方法2.类似于结点前插中的交换数据域,时间复杂度为O(1)

方法2的实质就是,将被删除结点*p的后继的data值,赋给*p,然后再删除后继,使得时间复杂度为O(1):

(由于单链表的局限性,方法2会受到限制,比如需要删除的结点是最后一个结点。它的next指针将会指向NULL,方法失效。     被指定删除的结点是最后一个结点时,需要做特殊处理。)


单链表的查找

按位查找

//按位查找,返回第i个元素(带头结点)LNode * GetElem(LinkList L, int i){if(inext;if(i==0) return L; //所要查找即为头结点,即第0个结点if(inext; //从第一个结点(头结点之后的结点)开始查找while(p != NULL && p->data != e)p = p->next;return p; //找到后返回该结点指针,否则返回NULL } 

平均时间复杂度O(n)

求链表长度

//求链表长度(带头结点)int length(LinkList L){int len = 0; //统计表长,初始表长为0LNode *p = L; //结点p指向表头,即第0个结点while(p->next != NULL){p = p->next;len++;} return len;} 

时间复杂度O(n)


尾插法与头插法 (带头结点)

给了很多数据元素,如何把它们存到一个单链表里?

step1:初始化一个单链表

step2:每次取一个数据元素,插入到表头/表尾

尾插法建立单链表

尾插法生成的链表结点次序和输入数据顺序一致

步骤: 1.初始化单链表  2.设置尾指针r,始终指向链表尾部结点

            3.while循环{每次取一个数据元素插到尾部}

//尾插法,正向建立单链表LinkList List_TailInsert(LinkList &L){int x;      //设元素类型为整型L = (LNode*)malloc(sizeof(LNode)); //创建头结点,申请存储空间LNode *s,*r = L;  //r为表尾指针scanf("%d",&x); //输入结点值//建立循环,开始插入元素,直接插到表尾 while(x != 999){ //999只是一个标志着结束的值,可以任意替换 s = (LNode *)malloc(sizeof(LNode)); //为结点申请空间s -> data = x; //赋值r -> next = s; //结点s插入到尾指针r所指结点的后方r = s; //尾指针r向后移动一位,指向新的表尾结点 scanf("%d",&x); } r -> next = NULL; //尾结点指针置空return L; } 

时间复杂度为O(n)

头插法建立单链表

头插法 数据读入的顺序与生成的链表中元素的顺序是相反的,可以用来实现链表的逆置。总时间复杂度为O(n),单链表表长为n。

步骤: 1.初始化单链表 2.while循环{每次取一个数据元素e;;}

每次将读取到的数据元素插入到当前链表的头结点之后。

//头插法 逆向建立单链表 LinkList List_HeadInsert(LinkList &L){int x; //数据元素为整型L = (LNode *)malloc(sizeof(LNode)); //创建头结点 L->next = NULL; //初始为空链表LNode *s; //设置指针scanf("%d",&x); //输入结点值while(x != 999){s = (LNode *)malloc(sizeof(LNode)); //创建新结点s -> data = x; //赋值s -> next = L->next ;L -> next = s; //将新结点插入表中,L为头指针scanf("%d",&x);  }  return L;} 

时间复杂度为O(n)


双链表

双链表有两个指针prior和next,分别指向直接前驱和直接后继。

表头结点的prior域和尾结点的next域都是NULL.

双链表中结点类型的描述如下:

//双链表类型定义typedef struct DNode{int data;  //数据域 struct DNode *prior,*next; //前驱和后继指针 }DNode,*DLinkList; 

双链表的按值查找和按位查找操作与单链表相同,但删除和查找操作比较容易断链,此外,双链表可以很容易的找到当前结点的前驱,插入、删除操作的时间复杂度仅为O(1)。

由于双链表的每个结点多了一个指针域,所以存储密度会低一些。

双链表的初始化(带头结点)

//初始化双链表bool InitDLinkList(DLinkList &L){L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点if(L == NULL) return false; //内存不足,分配失败L ->prior = NULL ; //头结点的prior指向NULL L ->next = NULL ;  //头结点之后暂时没有结点return true; } 

双链表的插入

//双链表的插入//在p结点之后插入s结点bool InsertNextDNode(DNode *p,DNode *s){if(p == NULL || s==NULL) return false; //非法参数s ->next = p->next;if(p->next != NULL) //如果p结点有后继结点 p->next->prior = s; s -> prior = p;p -> next = s;return true;} 

双链表的删除

//删除p结点的后继结点qbool DeleteNextDNode(DNode *p){if(p == NULL) return false; //非法DNode *q = p->next ; //找到p的后继结点qif (q == NULL) return false; //p没有后继结点p->next = q->next;if(q->next != NULL) //q不是最后一个结点q->next->prior = p;free(q); //释放结点空间return true; } 

双链表的遍历

上一篇:删除本地服务器怎么删除?

下一篇:华为服务器怎么恢复系统?