线性表——链表

顺序表需要事先占用一整块实现分配大小的存储空间,但是对于某些问题:很多空间只使用一次(甚至根本用不到),使用顺序表存储空间的利用率往往很低。于是需要一种能够动态管理存储空间的存储结构——链表。


线性表的链式存储结构——链表

线性表的链式存储结构我们简称为链表。其中每个存储结点不仅包含元素本身的信息(称为数据域),而且包含表示元素之间逻辑关系的信息,在C和C++中用指针实现,称为指针域。

一种最常见,最基本的方法是在每个结点除包有数据域以外只设置一个指针域,用于指向其后继结点,这样构成的链表称为线性单向链接表,简称单链表。另外一种给每个结点设置两个指针域,分别指向前继结点和后继结点,这样构成的链表我们称为线性双向链接表,简称双链表。

简单点说,每个结点只包含一个指针域指向后面的元素,生成的链表,我们即可称为线性链表或者单链表,包含两个指针分别指向前面和后面元素生成的链表,称为双链表。

头结点:有时,在链表的第一个结点之前会额外增设一个结点,结点的数据域一般不存放数据(有些情况下也可以存放链表的长度等信息),此结点被称为头结点,相应的也有尾结点和尾指针。

顺序表在根据索引查找方面比较快,但是在插入和删除方面比较耗时。链表查找比较慢,但是在插入和删除方面非常快速。并且顺序表的存储密度比较高,存储密度即指结点中数据元素本身所占的存储量和整个结点占用的存储之比,简单来说,就是存储空间的利用率比较高。

单链表的存储密度为50%,顺序表为100%。


单链表和循环单链表

和前面一样,用结构体来建立单链表,但是在顺序表那里,我们可用数组来存储连续的数据类型,而在链表这里,我们不知道什么容器在有数据的情况下,还有一个指向后面元素的指针,于是需要创建一个表示结点的结构体(两种链表结点结构体的定义方式是一样的):

typedef int ElemType;//依旧默认数据类型为int
struct LinkNode{ElemType data; struct LinkNode* next;};//定义单链表结点类型

单链表和循环单链表基本运算的实现

这里会提供单链表和循环单链表基本运算的实现及代码

建立单链表——头插法

和名字一样,每次将遍历到的新元素插入到头结点之后,最后得到的数据结点的顺序与数组a中元素的顺序是相反的:

void CreateListF(LinkNode*& L,ElemType a[],int n){LinkNode* s;//头插法建立单链表L=(LinkNode*)malloc(sizeof(LinkNode)); L->next=NULL;//创建头结点for (int i=0;i<n;i++){s=(LinkNode*)malloc(sizeof(LinkNode));//创建新结点s->data=a[i];//s表示添加进去的结点 s->next=L->next; L->next=s;//每次将遍历到的结点插入到头结点之后 }//这种插入方法可以将a数组的元素全部插入到链表中//但是最终单链表中数据结点的顺序与数组a中的元素相反
}

循环单链表和单链表的区别就是,需要查找尾结点,然后将尾结点的next域指向头结点,单链表直接指向NULL即可:

void CreateListF(LinkNode*& L,ElemType a[],int n){LinkNode* s;//头插法建立循环单链表L=(LinkNode*)malloc(sizeof(LinkNode)); L->next=NULL;//创建头结点for (int i=0;i<n;i++){s=(LinkNode*)malloc(sizeof(LinkNode));//创建新结点s->data=a[i];//s表示添加进去的结点 s->next=L->next; L->next=s;//每次将遍历到的结点插入到头结点之后 }s=L->next;  while (s->next!=NULL) s=s->next; s->next=L; //查找尾结点,将尾结点next域指向头结点//这种插入方法可以将a数组的元素全部插入到链表中//但是最终单链表中数据结点的顺序与数组a中的元素相反
}

建立单链表——尾插法

头插法是每次将新添加进的元素加入到头结点之后,尾插法则是单独创建一个结点r用于作为当前链表的最后一个有效结点(即存放数据),每次添加一个新元素,就将新添加的元素插入到结点r之后,更新结点r:

//尾插法建立单链表,s表示新结点,r表示最后一个结点
void CreateListR(LinkNode *&L,ElemType a[],int n){LinkNode *s,*r;L=(LinkNode*)malloc(sizeof(LinkNode)); L->next=NULL;//创建头结点r=L;//r始终指向尾结点,开始时指向头结点for (int i=0;i<n;i++){s=(LinkNode *)malloc(sizeof(LinkNode)); s->data=a[i];r->next=s; r=s;//这一步是将结点s插入结点r之后,但是理解起来比较费劲(对我)//这里r原本指向的是原链表的最后一个结点,现在需要添加进一个新的元素在r之后//r->next=s即表示,原链表的最后一个结点之后再添加一个新的结点//此时链表的最后一个结点不再是原本的r,而是新添加进去的s//于是更新r=s }r->next=NULL;//这里将最后一个节点指向NULL即可
}

如果建立循环链表就是将最后一个结点的指向改成头结点,由于尾插法始终用r表示最后一个结点,所以做起来还是比较方便的:

//尾插法建立循环单链表,s表示新结点,r表示最后一个结点
void CreateListR(LinkNode *&L,ElemType a[],int n){LinkNode *s,*r;L=(LinkNode*)malloc(sizeof(LinkNode)); L->next=NULL;//创建头结点r=L;//r始终指向尾结点,开始时指向头结点for (int i=0;i<n;i++){s=(LinkNode *)malloc(sizeof(LinkNode)); s->data=a[i];r->next=s; r=s;//这一步是将结点s插入结点r之后,但是理解起来比较费劲(对我)//这里r原本指向的是原链表的最后一个结点,现在需要添加进一个新的元素在r之后//r->next=s即表示,原链表的最后一个结点之后再添加一个新的结点//此时链表的最后一个结点不再是原本的r,而是新添加进去的s//于是更新r=s }r->next=L;//这里将最后一个节点指向第一个节点,建立的是循环链表
}

初始化线性表

就创建一个头结点即可,头结点的next域置NULL:

//初始化线性表,初始化的线性表只有一个结点
void InitList(LinkNode*& L){L=(LinkNode*)malloc(sizeof(LinkNode));L->next=NULL;}

循环表的话需要自己指向自己

//初始化线性表,第一个结点即是最后一个结点
//而循环表最后一个结点需要指向第一个结点,即自己指向自己
void InitList(LinkNode*& L){L=(LinkNode*)malloc(sizeof(LinkNode));L->next=L;}

销毁线性表

即释放掉链表中所有结点的空间,但是做的时候需要注意一个问题:就是当你释放掉某个结点的空间时,你还能通过这个结点的next找到下一个需要释放的空间么?

//销毁线性表
void DestroyList(LinkNode*& L){LinkNode *p=L,*q=p->next;//考虑到前面提出的问题,我们需要用q提前存放下一个结点 while (q!=NULL){//判断p是否为尾结点,如果p不是尾节点,则需要更新它后面的结点 free(p); p=q; q=p->next;}free(p);//如果是尾结点,则直接销毁
}

这里没什么区别,就是判断尾结点的条件不同:

//销毁线性表
void DestroyList(LinkNode*& L){LinkNode *p=L,*q=p->next;//考虑到前面提出的问题,我们需要用q提前存放下一个结点 while (q!=L){//判断p是否为尾结点,如果p不是尾节点,则需要更新它后面的结点 free(p); p=q; q=p->next;}free(p);//如果是尾结点,则直接销毁
}

判断线性表是否为空

线性表为空时,链表除了头结点以外没有别的结点,判断头结点是否满足尾结点的性质即可:

//判断线性表是否为空
bool ListEmpty(LinkNode* L){return(L->next==NULL);}

判断条件不同,和前一个功能一样:

//判断线性表是否为空
bool ListEmpty(LinkNode* L){return(L->next==L);}

求线性表的长度

一个一个往后遍历,直到遍历到尾结点即可:

//求线性表的长度
int ListLength(LinkNode* L){LinkNode *p=L; int len=0;//一边从头结点一个一个往后遍历,一边用len计数,遍历到尾结点时,len即为链表长度 while (p->next!=NULL){len++; p=p->next;} return(len);
}

也是判断尾结点的方法不同:

//求线性表的长度
int ListLength(LinkNode* L){LinkNode *p=L; int len=0;//一边从头结点一个一个往后遍历,一边用len计数,遍历到尾结点时,len即为链表长度 while (p->next!=L){len++; p=p->next;} return(len);
}

输出线性表

和销毁线性表一样,一个一个往后遍历即可:

//输出线性表
void DispList(LinkNode* L){LinkNode *p=L->next;while (p!=NULL) {printf("%d ",p->data); p=p->next;} printf("\n");
}

也是判断尾结点的方法不同:

//输出线性表
void DispList(LinkNode* L){LinkNode *p=L->next;while (p!=L) {printf("%d ",p->data); p=p->next;} printf("\n");
}

求线性表中的某个数据的元素值

就是求第i个元素,不过比起顺序表麻烦的是:顺序表的长度直接可以通过length得到,链表还需要用j来记录当前遍历的长度。

//求线性表中的某个数据元素值
bool GetElem(LinkNode* L,int i,ElemType &e){int j=0; LinkNode* p=L;if (i<=0) return false;//i的输入非法 while (j<i&&p!=NULL){j++; p=p->next;} if (p==NULL) return false;//表示遍历完所有的元素,j的值都小于i,说明不存在第i个数据结点 else {e=p->data; return true;}
}

如果是循环链表会困难一点,因为判断尾结点的条件是判断p是否等于L,那么如果p初始化为L,会直接从while跳出:

//求线性表中的某个数据元素值
bool GetElem(LinkNode* L,int i,ElemType &e){int j=0; LinkNode* p=L->next; i--;
//这里为什么设置为next而不是直接设置为L呢?因为老师定义的是一个循环表
//最后一个结点指向头结点,按照p!=L判断时,如果p初始化为L会出现错误
//p初始化为L->next,相当于j就是直接从1开始,我这里就直接i-- if (i<0) return false;//i的输入非法,由于减过1,小于0就非法了 while (j<i&&p!=L){j++; p=p->next;} if (p==L) return false;//表示遍历完所有的元素,j的值都小于i,说明不存在第i个数据结点 else {e=p->data; return true;}
}

我这里的做法和书上的源码不一样,书上将空表的情况单独拿出来,空表的情况我是考虑过的:L=L->next。也就是说p此时等于L,会直接跳出while循环,仍保持L返回false,空表中确实没有元素(恰好吻合了?)

然后书上还单独讨论了i=1的情况,给我的函数代入i=1,i-1=0,也是直接跳出while循环,但是此时p=L->next,也满足要求。

其他的部分就一样了,但还是出于尊重看一下书上的代码······

想多了,我不是这样的人hhhhh

按元素值查找

和上面一个做法是一样的,事实上比上面一个基本操作写起来还要简单一点:

//按元素值查找
int LocateElem(LinkNode* L,ElemType e){LinkNode* p=L->next; int pos=1;while (p!=NULL&&p->data!=e){p=p->next; pos++;}//pos表示遍历到第pos个元素 //p等于L,代表遍历完所有的元素后,还没有找到元素值等于e的数据结点,于是打印0 if (p==NULL) return(0); else return(pos);
}

循环链表:

//按元素值查找
int LocateElem(LinkNode* L,ElemType e){LinkNode* p=L->next; int pos=1;while (p!=L&&p->data!=e){p=p->next; pos++;}//pos表示遍历到第pos个元素 //p等于L,代表遍历完所有的元素后,还没有找到元素值等于e的数据结点,于是打印0 if (p==L) return(0); else return(pos);
}

插入数据元素

插入是链表比较快捷的一个基本运算,只需要找到插入的位置,在结点后面插入需要添加的结点即可,由于没有索引,所以不会破坏后面的结构:

//插入数据元素
bool ListInsert(LinkNode*& L,int i,ElemType e){int j=0; LinkNode *p=L,*s;//s为创建的新节点 if (i<=0) return false;//i的值非法 while (j<i-1&&p!=NULL) {j++; p=p->next;}//查找第i-1个结点pif (p==NULL) return false;//表示遍历完所有的元素,j的值都小于i,说明不存在第i-1个数据结点else{s=(LinkNode *)malloc(sizeof(LinkNode));//创建新结点插入到p之后 s->data=e; s->next=p->next; p->next=s; return true;}
}

循环链表的插入也是按照我自己的方法做的:

//插入数据元素
bool ListInsert(LinkNode*& L,int i,ElemType e){int j=0; i--; LinkNode *p=L->next,*s;//s为创建的新节点,为了防止与第一个结点冲突,仍然将p初始化为L->next if (i<0) return false;//i的值非法 while (j<i-1&&p!=L) {j++; p=p->next;}//查找第i-1个结点p(这里说的i都是原始i) if (p==L) return false;//表示遍历完所有的元素,j的值都小于i,说明不存在第i-1个数据结点else{s=(LinkNode *)malloc(sizeof(LinkNode));//创建新结点插入到p之后 s->data=e; s->next=p->next; p->next=s; return true;}
}

但是这么做有个问题,就是循环单链表如果是空表和i=1时会直接判错和跳出循环,于是我们需要对两种情况单独讨论:

//插入数据元素
bool ListInsert(LinkNode*& L,int i,ElemType e){int j=0; i--; LinkNode *p=L->next,*s;//s为创建的新节点,为了防止与第一个结点冲突,仍然将p初始化为L->next if (i<0) return false;//i的值非法 //如果为空表,只能插入第一个,其他都为违法操作,于是可以将空表和i=1的情况归并 if (i==0){s=(LinkNode *)malloc(sizeof(LinkNode));s->data=e; s->next=L->next; L->next=s; return true;}  while (j<i-1&&p!=L) {j++; p=p->next;}//查找第i-1个结点p(这里说的i都是原始i) if (p==L) return false;//表示遍历完所有的元素,j的值都小于i,说明不存在第i-1个数据结点else{s=(LinkNode *)malloc(sizeof(LinkNode));//创建新结点插入到p之后 s->data=e; s->next=p->next; p->next=s; return true;}
}

事实上两种情况可以归并为i=1的特殊情况。

删除数据元素

需要注意的是删除第i个元素,实际上需要寻找的是第i-1个元素,找到之后还要判断是否存在第i个元素:

//删除数据元素
bool ListDelete(LinkNode*& L,int i,ElemType &e){int j=0; LinkNode *p=L,*q;if (i<=0) return false;//i的值非法 while (j<i-1&&p!=NULL) {j++; p=p->next;}//查找第i-1个结点if (p==NULL) return false; //表示遍历完所有的元素,j的值都小于i,说明不存在第i-1个数据结点,或者是空表else{q=p->next;//q指向要删除的结点if (q==NULL) return false;//若不存在第i个结点,即q是最后一个元素,删除失败 e=q->data; p->next=q->next;   free(q); return true;//删除结点q,即第i个结点 }
}

和插入一样,但依旧可以比较完美的处理“特殊情况”:

//删除数据元素
bool ListDelete(LinkNode*& L,int i,ElemType &e){int j=0; LinkNode *p=L->next,*q; i--; if (i<0) return false;//i的值非法 while (j<i-1&&p!=L) {j++; p=p->next;}//查找第i-1个结点if (p==L) return false; //表示遍历完所有的元素,j的值都小于i,说明不存在第i-1个数据结点,或者是空表 else if (i==0){q=L->next; L->next=L->next->next; free(q); return true;}//删除第一个元素 else{q=p->next;//q指向要删除的结点if (q==L) return false;//若不存在第i个结点,即q是最后一个元素,删除失败 e=q->data; p->next=q->next; free(q); return true;//删除结点q,即第i个结点 }
}

单链表基本运算合集如下:

typedef int ElemType;//依旧默认数据类型为int
struct LinkNode{ElemType data; struct LinkNode* next;};//定义单链表结点类型
void CreateListF(LinkNode*& L,ElemType a[],int n){LinkNode* s;//头插法建立单链表L=(LinkNode*)malloc(sizeof(LinkNode)); L->next=NULL;//创建头结点for (int i=0;i<n;i++){s=(LinkNode*)malloc(sizeof(LinkNode));//创建新结点s->data=a[i];//s表示添加进去的结点 s->next=L->next; L->next=s;//每次将遍历到的结点插入到头结点之后 }//这种插入方法可以将a数组的元素全部插入到链表中//但是最终单链表中数据结点的顺序与数组a中的元素相反
}//尾插法建立单链表,s表示新结点,r表示最后一个结点
void CreateListR(LinkNode *&L,ElemType a[],int n){LinkNode *s,*r;L=(LinkNode*)malloc(sizeof(LinkNode)); L->next=NULL;//创建头结点r=L;//r始终指向尾结点,开始时指向头结点for (int i=0;i<n;i++){s=(LinkNode *)malloc(sizeof(LinkNode)); s->data=a[i];r->next=s; r=s;//这一步是将结点s插入结点r之后,但是理解起来比较费劲(对我)//这里r原本指向的是原链表的最后一个结点,现在需要添加进一个新的元素在r之后//r->next=s即表示,原链表的最后一个结点之后再添加一个新的结点//此时链表的最后一个结点不再是原本的r,而是新添加进去的s//于是更新r=s }r->next=NULL;//这里将最后一个节点指向NULL即可
}//初始化线性表,初始化的线性表只有一个结点
void InitList(LinkNode*& L){L=(LinkNode*)malloc(sizeof(LinkNode));L->next=NULL;}
//销毁线性表
void DestroyList(LinkNode*& L){LinkNode *p=L,*q=p->next;//考虑到前面提出的问题,我们需要用q提前存放下一个结点 while (q!=NULL){//判断p是否为尾结点,如果p不是尾节点,则需要更新它后面的结点 free(p); p=q; q=p->next;}free(p);//如果是尾结点,则直接销毁
}//判断线性表是否为空
bool ListEmpty(LinkNode* L){return(L->next==NULL);}
//求线性表的长度
int ListLength(LinkNode* L){LinkNode *p=L; int len=0;//一边从头结点一个一个往后遍历,一边用len计数,遍历到尾结点时,len即为链表长度 while (p->next!=NULL){len++; p=p->next;} return(len);
}//输出线性表
void DispList(LinkNode* L){LinkNode *p=L->next;while (p!=NULL) {printf("%d ",p->data); p=p->next;} printf("\n");
}//求线性表中的某个数据元素值
bool GetElem(LinkNode* L,int i,ElemType &e){int j=0; LinkNode* p=L;if (i<=0) return false;//i的输入非法 while (j<i&&p!=NULL){j++; p=p->next;} if (p==NULL) return false;//表示遍历完所有的元素,j的值都小于i,说明不存在第i个数据结点 else {e=p->data; return true;}
}//按元素值查找
int LocateElem(LinkNode* L,ElemType e){LinkNode* p=L->next; int pos=1;while (p!=NULL&&p->data!=e){p=p->next; pos++;}//pos表示遍历到第pos个元素 //p等于L,代表遍历完所有的元素后,还没有找到元素值等于e的数据结点,于是打印0 if (p==NULL) return(0); else return(pos);
}//插入数据元素
bool ListInsert(LinkNode*& L,int i,ElemType e){int j=0; LinkNode *p=L,*s;//s为创建的新节点 if (i<=0) return false;//i的值非法 while (j<i-1&&p!=NULL) {j++; p=p->next;}//查找第i-1个结点pif (p==NULL) return false;//表示遍历完所有的元素,j的值都小于i,说明不存在第i-1个数据结点else{s=(LinkNode *)malloc(sizeof(LinkNode));//创建新结点插入到p之后 s->data=e; s->next=p->next; p->next=s; return true;}
}//删除数据元素
bool ListDelete(LinkNode*& L,int i,ElemType &e){int j=0; LinkNode *p=L,*q; if (i<=0) return false;//i的值非法 while (j<i-1&&p!=NULL) {j++; p=p->next;}//查找第i-1个结点if (p==NULL) return false; //表示遍历完所有的元素,j的值都小于i,说明不存在第i-1个数据结点else{q=p->next;//q指向要删除的结点if (q==NULL) return false;//若不存在第i个结点,即q是最后一个元素,删除失败 e=q->data; p->next=q->next;    free(q); return true;//删除结点q,即第i个结点 }
}

循环单链表基本运算合集如下:

typedef int ElemType;//依旧默认数据类型为int
struct LinkNode{ElemType data; struct LinkNode* next;};//定义单链表结点类型
void CreateListF(LinkNode*& L,ElemType a[],int n){LinkNode* s;//头插法建立循环单链表L=(LinkNode*)malloc(sizeof(LinkNode)); L->next=NULL;//创建头结点for (int i=0;i<n;i++){s=(LinkNode*)malloc(sizeof(LinkNode));//创建新结点s->data=a[i];//s表示添加进去的结点 s->next=L->next; L->next=s;//每次将遍历到的结点插入到头结点之后 }s=L->next; while (s->next!=NULL) s=s->next; s->next=L; //查找尾结点,将尾结点next域指向头结点//这种插入方法可以将a数组的元素全部插入到链表中//但是最终单链表中数据结点的顺序与数组a中的元素相反
}//尾插法建立循环单链表,s表示新结点,r表示最后一个结点
void CreateListR(LinkNode *&L,ElemType a[],int n){LinkNode *s,*r;L=(LinkNode*)malloc(sizeof(LinkNode)); L->next=NULL;//创建头结点r=L;//r始终指向尾结点,开始时指向头结点for (int i=0;i<n;i++){s=(LinkNode *)malloc(sizeof(LinkNode)); s->data=a[i];r->next=s; r=s;//这一步是将结点s插入结点r之后,但是理解起来比较费劲(对我)//这里r原本指向的是原链表的最后一个结点,现在需要添加进一个新的元素在r之后//r->next=s即表示,原链表的最后一个结点之后再添加一个新的结点//此时链表的最后一个结点不再是原本的r,而是新添加进去的s//于是更新r=s }r->next=L;//这里将最后一个节点指向第一个节点,建立的是循环链表
}//初始化线性表,第一个结点即是最后一个结点
//而循环表最后一个结点需要指向第一个结点,即自己指向自己
void InitList(LinkNode*& L){L=(LinkNode*)malloc(sizeof(LinkNode));L->next=L;}
//销毁线性表
void DestroyList(LinkNode*& L){LinkNode *p=L,*q=p->next;//考虑到前面提出的问题,我们需要用q提前存放下一个结点 while (q!=L){//判断p是否为尾结点,如果p不是尾节点,则需要更新它后面的结点 free(p); p=q; q=p->next;}free(p);//如果是尾结点,则直接销毁
}//判断线性表是否为空
bool ListEmpty(LinkNode* L){return(L->next==L);}
//求线性表的长度
int ListLength(LinkNode* L){LinkNode *p=L; int len=0;//一边从头结点一个一个往后遍历,一边用len计数,遍历到尾结点时,len即为链表长度 while (p->next!=L){len++; p=p->next;} return(len);
}//输出线性表
void DispList(LinkNode* L){LinkNode *p=L->next;while (p!=L) {printf("%d ",p->data); p=p->next;} printf("\n");
}//求线性表中的某个数据元素值
bool GetElem(LinkNode* L,int i,ElemType &e){int j=0; LinkNode* p=L->next; i--;
//这里为什么设置为next而不是直接设置为L呢?因为老师定义的是一个循环表
//最后一个结点指向头结点,按照p!=L判断时,如果p初始化为L会出现错误
//p初始化为L->next,相当于j就是直接从1开始,我这里就直接i-- if (i<0) return false;//i的输入非法,由于减过1,小于0就非法了 while (j<i&&p!=L){j++; p=p->next;} if (p==L) return false;//表示遍历完所有的元素,j的值都小于i,说明不存在第i个数据结点 else {e=p->data; return true;}
}//按元素值查找
int LocateElem(LinkNode* L,ElemType e){LinkNode* p=L->next; int pos=1;while (p!=L&&p->data!=e){p=p->next; pos++;}//pos表示遍历到第pos个元素 //p等于L,代表遍历完所有的元素后,还没有找到元素值等于e的数据结点,于是打印0 if (p==L) return(0); else return(pos);
}//插入数据元素
bool ListInsert(LinkNode*& L,int i,ElemType e){int j=0; i--; LinkNode *p=L->next,*s;//s为创建的新节点,为了防止与第一个结点冲突,仍然将p初始化为L->next if (i<0) return false;//i的值非法 //如果为空表,只能插入第一个,其他都为违法操作,于是可以将空表和i=1的情况归并 if (i==0){s=(LinkNode *)malloc(sizeof(LinkNode));s->data=e; s->next=L->next; L->next=s; return true;}  while (j<i-1&&p!=L) {j++; p=p->next;}//查找第i-1个结点p(这里说的i都是原始i) if (p==L) return false;//表示遍历完所有的元素,j的值都小于i,说明不存在第i-1个数据结点else{s=(LinkNode *)malloc(sizeof(LinkNode));//创建新结点插入到p之后 s->data=e; s->next=p->next; p->next=s; return true;}
}//删除数据元素
bool ListDelete(LinkNode*& L,int i,ElemType &e){int j=0; LinkNode *p=L->next,*q; i--; if (i<0) return false;//i的值非法 while (j<i-1&&p!=L) {j++; p=p->next;}//查找第i-1个结点if (p==L) return false; //表示遍历完所有的元素,j的值都小于i,说明不存在第i-1个数据结点,或者是空表 else if (i==0){q=L->next; L->next=L->next->next; free(q); return true;}//删除第一个元素 else{q=p->next;//q指向要删除的结点if (q==L) return false;//若不存在第i个结点,即q是最后一个元素,删除失败 e=q->data; p->next=q->next; free(q); return true;//删除结点q,即第i个结点 }
}

应用举例

例 2.6

有一个带头结点的单链表L=(a1,b1,a2,b2·····an,bn),设计一个算法将其拆分为两个带头结点的单链表L1和L2,其中L1=(a1,a2·····an),L2=(bn,b(n-1)·····b1)。

分析:一看L1是正序的,L2是逆序的,说明L1是尾插法,L2是头插法:

void split(LinkNode *&L,LinkNode *&L1,LinkNode *&L2){LinkNode *p=L->next,*q,*r1;//p指向L的第1个数据结点,r1始终指向L1的尾结点(尾插法) L1=L; r1=L1;//L1则延用原来L的头结点        L2=(LinkNode*)malloc(sizeof(LinkNode)); L2->next=NULL;//创建L2的头结点while (p!=NULL){ r1->next=p; r1=p;//采用尾插法将结点p(值为ai)插入r1之后,更新r1 p=p->next; q=p->next;//由于头插法修改p的next域,所以要用q保存结点p的next结点 p->next=L2->next; L2->next=p;//采用头插法将结点p(值为bj)插入L2中p=q;//将p重新指向链表L的下一个结点 }r1->next=NULL;//尾插法需要置空(事实上结构体初始化好的化也不需要)
}

自己编写代码要注意一个问题,就是使用头插法修改p的next域时,为了还能够正常遍历,而不是错进了另一个链表,需要预先存储next指向的结点。这是多个链表的操作中常容易出现的错误和比较常见的处理方式。

例 2.7

设计一个算法,删除链表L中元素最大的结点(假设该元素唯一)。

分析:这个问题放在顺序表中大家都会,链表处理的难点就在于,删除单链表的一个结点,应该找它的上一个结点。于是我们用p来找元素值最大的结点,pre来存储p的前一个结点即可:

void delmaxnode(LinkNode *&L){LinkNode *p=L->next,*pre=L,*maxp=p,*maxpre=pre;//由于删除结点实际上是找被删除结点的前一个结点//于是我们用p来扫描链表,pre存储p的前一个结点来进行删除 while (p!=NULL){ if (maxp->data<p->data) {maxp=p; maxpre=pre;}pre=p; p=p->next;//两个指针同步更新 }maxpre->next=maxp->next; free(maxp);//删除maxp结点
}

例 2.8

有一个带头结点的单链表L,保证L不为空链表,设计算法使元素递增排序。

分析:就是排序问题,书上的方法相当于插入排序,我只能说代码挺抽象的:

//链表的插入排序
void sort(LinkNode *&L){LinkNode *p=L->next->next,*pre,*q;//p指向L的第2个数据结点L->next->next=NULL;//这里相当于建立了一个只有L->next->data一个元素的有序表//p存储了L->next->next,就用p来扫描后面的元素将元素一个一个插入到有序表中 //有序表在这个过程中始终保持有序,即是插入排序,只不过在链表中会有点抽象//因为顺序表的数组,可以用length来记录长度,比较大小交换位置即可 //但是链表中这两者都比较困难,于是直接将第一个结点的指向改变,建立一个新的有序表//然后将原表中剩余的元素插入到这个新表中 while (p!=NULL){q=p->next;//q的作用仍然是保存被修改之前p的next域//这里pre遍历的即是已经排好序的有序表,在有序表中找一个可以插入的位置//由于是从小到大递增排序,插入位置的data值需要小于被插入的元素值//而插入位置下一个结点的data值需要大于被插入的元素值          pre=L; while (pre->next!=NULL&&pre->next->data<p->data) pre=pre->next; p->next=pre->next; pre->next=p;//在pre结点之后插入p结点p=q;//继续扫描(插入)原单链表余下的结点}
}

做了比较详细的分析,大家看看就好,毕竟你让我自己写,我也未必写的出来。

第二章——单链表和循环单链表相关推荐

  1. 【数据结构】线性表的链式表示-循环单链表、循环双链表、静态链表

    循环单链表 从任何一个结点出发都能访问到链表的每一个元素 判空条件不是头节点的后继指针是否为空,而是它是否等于头指针 有时对单链表常做的操作实在表头和表尾进行的,此时可对循环单链表不设头指针而仅设尾指 ...

  2. 【数据结构】之双向链表、双端(双向)链表、循环(双向)链表

    双向链表.双端(双向)链表.循环(双向)链表示意(简)图: 声明:下面各图中,箭头指向的是整个节点,而不是指向节点中的prior或next. 双向链表:只有一个指针指向最开始的结点. 双端(双向)链表 ...

  3. (王道408考研数据结构)第二章线性表-第三节3:循环单链表和循环双链表

    文章目录 一:循环链表定义 二:循环单链表 三:循环双链表 一:循环链表定义 循环链表:规定好头尾结点的指向形成成环状 循环单链表:其尾节点的next指针由原本的空改为指向头结点 循环双链表:其尾节点 ...

  4. 【C语言笔记初级篇】第二章:分支与循环

    目录 (1)什么是语句 (2)分支语句 A:if语句 B:switch语句 (3)循环语句 A:while B:for循环 C:do while循环 (4)goto语句 A:goto语句说明 B:go ...

  5. 第二章:2线性表---单链表表示和实现

    前言: 为避免在使用线性表顺序存储结构的时,需插入和删除需大量移动元素的弊端. 本节讨论线性表的另外一种表示方法---链式存储结构: 由于它不要求逻辑上相邻的元素在物理位置上相邻,因此它对元素的插入和 ...

  6. yxc_第二章 数据结构(一)_链表

    写在前面:链表这一部分完结,我觉得链表最重要的还是根据题目来写代码(数组模拟指针),可能是大二上已经学过应试版数据结构的缘故吧.最后,还是希望自己能够持之以恒,在寒假完成算法基础课.(最近几天都睡到了 ...

  7. Python程序开发——第二章 条件语句和循环语句

    目录 前言 一.if语句 (一)单分支 (二)双分支 (三)多分支 (四)if语句的嵌套 二.for循环 (一)for循环的定义 (二)range()函数控制循环次数 (三)for循环中的else子句 ...

  8. 【Python】第二章(条件语句和循环语句)

    各位小伙伴们大家好,在此之前我跟大家介绍过[python]的第一章,如果还没有看的小伙伴们可以去看看,这是链接:[Python]第一章(建议收藏)_泽奀的博客-CSDN博客,蟹蟹支持.那么本篇博客讲的 ...

  9. python 学习 之 第二章(条件、循环和其他语句)

    1.    简单的条件执行语句( if ) num = int( input("enter a number\n") ) if num == 0: print("num ...

最新文章

  1. vue实用组件——页面公共头部
  2. mysql win10 5.5_win10上MySql5.5版本升级到5.7
  3. c++ template笔记(3)非类型模板参数nontype template parameters
  4. 【Spring Cloud笔记】 Eureka通过集群实现高可用
  5. Oracle RMAN备份与还原 - 脱机备份讲解
  6. 标致雪铁龙与菲亚特克莱斯勒宣布完成合并 新股1月19日纽交所上市
  7. 幽默度识别第一名解决方案代码及说明
  8. 字面量(literal)与 C 语言复合字面量(compound literals)
  9. atitit 业务 触发器原理. 与事件原理 docx
  10. python身份证号真假验证_【趣味案例】用python制作全国身份证号验证及查询系统...
  11. leetcode 面试题57 - II. 和为s的连续正数序列
  12. mysql autoenlist默认_Dapper MySql DateTime 异常
  13. Transfer learning for deep neural network-based partial differential equations solving论文笔记
  14. 广州大喜事婚庆公司报价表
  15. Win10打开文件夹闪退怎么解决
  16. 网易伏羲私有云在资源调度及资源整合方面的实践
  17. pattern java怎么用_Pattern Java设计模式23种每个一个举例使用,名称直接对应英文,简单明了 Develop 238万源代码下载- www.pudn.com...
  18. P6专题:P6核心技术(多级计划,单代号,项目组合,关键路径,赢得值等)
  19. No link elements found in urdf file
  20. 软件开发真的是工资高、来钱快吗?聊聊我在这个行业的经历!

热门文章

  1. 企业如何进行网络安全意识培训
  2. Kubuntu 15.10 高清截图欣赏
  3. 我的专业我做主计算机演讲稿,我的未来我做主演讲稿
  4. FindDifferent - QQ游戏快速找茬工具
  5. QT编译libjpeg
  6. rgb三色直方图的绘制
  7. TUTK摄像头配网方式之AP模式配网
  8. MKS SKIPR V1.0 使用说明书
  9. [NOIP2002 普及组] 选数 dfs筛素数
  10. 数学实验测试软件,matlab数学软件实验测试题.doc