单链表
**单链表:**线性表的链式存储又称单链表。它是指通过一组任意的存储单元来存储线性表中的数据元素。每一个链表结点,除存放元素自身的信息外,还需要存放一个指向其后继的指针。
data | next |
---|
**双链表:**双链表结点中有两个指针prior和next,分别指向其前驱结点和后继结点。按值查找和按位查找的操作与单链表相同,插入删除操作时间复杂度仅为O(1)。
**循环单链表:**表中最后一个结点的指针不是NULL,而是指向头结点,从而整个链表形成一个环。可以从表中任意一个结点开始遍历整个链表。
**循环双链表:**头结点的prior指针指向表尾结点。
**静态链表:**静态链表借助数组来描述线性表的链式存储结构。结点也有数据域data和指针域next。这里的指针是指数组下标,与顺序表一样,静态链表也需要预先分配一块连续的内存空间。静态链表的插入删除操作与动态链表的相同,只需修改指针,而不需要移动元素。
注意:采用头插法时,要使用临时变量接收该结点的后继结点;采用尾插法时,要将尾指针指向NULL。
void Del_X_3(LinkList &L,ElemType x){
LNode *p;
if(L==NULL) //结束递归
return;
if(L->data==x){
p=L; //删除*L,并让L指向下一结点
L=L->next;
free(p);
Del_x_3(L,x); //递归调用
}
else
Del_x_3(L->next,x);
}
//解法1:用p从头至尾扫描单链表,pre指向*p结点的前驱。若p所指结点的值为x,则删除,并让p移向下一个结点,否则让pre、p指针同步向后移一个结点。
void Del_X_1(LinkList &L,ElemType x){
LNode *p=L->next,*pre=L,*q;
while(p!=NULL){
if(p->data==x){
q=p;
p=p->next;
pre->next=p;
free(p);
}
else{
pre=p;
p=p->next;
}
}
}
//解法2:采用尾插法建立单链表。用p指针扫描L的所有结点,当其值不为x时将其链接到L之后,否则将其释放。
void Del_x_2(LinkList &L,ElemType x){
LNode *p=L->next,*r=L,*q; //r为表尾指针
while(p->data!=x){
r->next=p; //将不为x的结点插入表尾
r=p;
p=p->next;
}
else{
q=p;
p=p->next;
free(q);
}
r-next=NULL; //插入结束后置表尾结点指针为NULL
}
//思路1:每访问一个结点,先递归输出它后面的结点,再输出该节点自身,这样链表就反向输出了。
void R_Print(LinkList L){
if(L->next!=NULL)
R_Print(L->next);
if(L!=NULL)
print(L->data);
}
//思路2:遍历单链表,将每个结点放入栈中,再从栈顶输出结点值。
//思路:用p从头扫描链表,pre指向*p的前驱,用minp保存最小值(初始值为p),minpre指向*minp结点的前驱。一边扫描,一边比较,若p->data小于minp->data,将p、pre分别赋值给minp、minpre。扫描完毕后,minp指向最小值结点,minpre指向最小值结点的前驱结点。再将minp所指结点删除。
LinkList Delete_Min(LinkList &L){
LNode *pre=L,*p=pre->next; //p为工作结点,pre指向其前驱
LNode *minpre=pre,*minp=p; //保存最小值及其前驱结点
while(p!=NULL){
if(p->data<minp->data){
minp=p;
minpre=pre;
}
pre=p;
p=p->next;
}
minpre->next=minp->next; //删除最小值结点
free(minp);
return L;
}
//思路1:将头结点摘下,然后从第一结点开始,依次插入到头结点的后面,直到最后一个结点为止。
LinkList Reverse_1(LinkList L){
LNode *p,*r;
p=L->next; //从第一个元素结点开始
L->next=NULL; //将头结点L的next域置为NULL
while(p!=NULL){
r=p->next; //暂存p的后继
p->next=L->next; //将p结点插入到头结点之后
L->next=p;
p=r;
}
return L;
}
//解法2:
LinkList Reverse_2(LinkList L){
LNode *pre,*p=L->next,*r=p->next;
p->next=NULL; //处理第一个结点
while(r!=NULL){
pre=p;
p=r;
r=r->next;
p->next=pre; //指针反转
}
L->next=p; //处理最后一个结点
return L;
}
//思路:采用直接插入排序算法的思想,先构成只含一个数据结点的有序单链表,然后依次扫描单链表中剩下的结点*p,在有序表中通过比较查找插入*p的前驱结点*pre,然后将*p插入到*pre之后。
void Sort(LinkList &L){
LNOde *p=L->next,*pre;
LNOde *r=p->next;
p->next=NULL; //构造只含一个数据结点的有序表
p=r;
while(p!=NULL){
r=p->next;
pre=L;
while(pre->next!=NULL&&pre->next->data<p->data)
pre=pre->next; //在有序表中查找插入*p的前驱结点*pre
p-next=pre->next; //将*p插入到*pre之后
pre->next=p;
p=r; //扫描原链表中剩下的结点
}
}
//思路:因为链表是无序的,所有只能逐个结点进行检查
void RangeDelete(LinkList &L,int min,int max){
LNode *pr=L,*p=L->next; //*pr是p的前驱,p是检查指针
while(p!=NULL){
if(p->data>min&&p->data<max){
pr->next=p->next; //删除找到的结点
free(p);
p=pr->next;
}
else{
pr=p;
p=p->next;
}
}
}
//思路:公共结点表示两个链表的共同部分,公共结点的第一个结点可以不在链表的头部,但结尾必是共同结点(如果存在)。所以先求两个链表的长度,遍历相对较长的链表到长度相等的位置,在该位置判断结点值是否相等,相等则返回,不相等则后移继续判断。
LinkList Search_1st_Comment(LinkList L1,LinkList L2){
int len1=Length(L1),len2=Length(L2),dist; //计算两个链表的链长
LinkList longList,shortList; //分别指向表长较长和较短的链表
if(len1>len2){
longList=L1->next;
shortList=L2->next;
dist=len1-len2; //表长之差
}else{
longList=L2->next;
shortList=L1->next;
dist=len2-len1;
}
while(dist--)
longList=longList->next; //表长的链表先遍历到第dist个结点,然后同步
while(longList!=NULL){
if(longList==shortList) //寻找共同结点
return longList;
else{
longList=longList->next;
shortList=shortList->next;
}
}
return NULL;
}
//时间复杂度O(len1+len2)
//思路:每次遍历找出整个链表的最小值元素,输出并释放结点所占空间。知道链表为空,最后释放头结点。时间复杂度为O(n的平方)
void Min_Delete(LinkList &head){ //head是带头结点的单链表的头指针
while(head->next!=NULL){ //循环到仅剩头结点,则释放头结点,否则进行下面操作
pre=head; //pre是最小值结点的前驱指针
p=pre->next;
while(p->next!=NULL){
if(p->next->data<pre->next->data){
pre=p; //记住当前最小值结点的前驱
p=p->next;
}
}
print(pre->next->data); //输出元素最小值结点的数据
u=pre->next; //释放结点空间
pre-next=u->next;
free(u);
}
free(head); //释放头结点
}
//思路:设置一个访问序号变量,每访问一个结点序号自动加,然后根据序号的奇偶性将结点插入A或B中。
LinkList DisCreat_1(LinkList &A){
i=0;
B=(LinkList)malloc(sizeof(LNode)); //创建B表表头
B->next=NULL; //B表的初始化
LNode *ra=A,*rb=B; //ra、rb分别指向将创建的A表和B表的尾结点
p=A->next; //p为工作指针
A->next=NULL; //制空新的A表
while(p!=NULL){
i++;
if(i%2==0){ //处理序号为偶数的链表结点
rb->next=p;
rb=p;
}else{ //处理序号为奇数的链表结点
ra->next=p;
ra=p;
}
p=p->next; //将p恢复为指向新的待处理的结点
}
ra->next=NULL;
rb->next=NULL;
return B;
}
//A采用尾插法。B采用头插法,*p的指针域已改变,需要变量保存其后继结点。
LinkList DisCreate_2(LinkList &A){
LinkList B=(LinkList)malloc(sizeof(LNode)); //创建B表表头
B->next=NULL; //B表的初始化
LNode *p=A->next,*q;
LNode *ra=A; //ra始终指向A的尾结点
while(p!=NULL){
ra->next=p; //将p链到A的表尾
ra=p;
p=p->next;
if(p!=NULL)
q=p->next; //头插法后,*p将断链,需要用q记忆*p的后继
p->next=B->next; //将*p查到B的前端
B->next=p;
p=q;
}
ra->next=NULL; //A尾结点的next域制空
return B;
}
-
在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,使表中不在有重复的元素,例如(7,10,10,21,30,42,42,42,51,70)将变为(7,10,21,30,42,51,70)。
//思路:用*p扫描递增单链表L,若*p结点的值域等于其后继结点的值域,则删除后者,否则p移向下一个结点。时间复杂度O(n)
void Del_Same(LinkList &L){
LNode *p=L->next,*q;
if(p==NULL)
return;
while(p->next!=NULL){
q=p->next; //q指向*p的后继结点
if(p->data==q->data){ //找到重复值的结点
p->next=q->next;
free(q);
}else
p=p->next;
}
}
//思路:合并时,均从第一个结点起进行比较,将小的结点链入链表中,应采用头插法,使元素中按次序递减。比较结束后,可能会有一个链表非空,此时用头插法将剩下的结点依次插入新链表中。
void MergeList(LinkList &La,LinkList &Lb){
LNode *r,*pa=La->next,*pb=Lb->next;
La->next=NULL;
while(pa&&pb){
if(pa->data<=pb->data){ //将pa结点头插入La中
r=pa->next;
pa->next=La->next;
La->next=pa;
pa=r;
}else{ //将pb结点头插入La中
r=pb->next;
pb->next=La->next;
La->next=pb;
pb=r;
}
}
if(pa) //如果pa链表不空,将pa指向pb
pb=pa;
while(pb){ //当pb不空时,将剩下元素结点头插入La中
r=pb->next;
pb->next=La->next;
La->next=pb;
pb=r;
}
free(Lb); //释放Lb链表空间
}
//思路:由于A、B表都有序,可从第一个元素起依次比较A、B两边的元素,若元素值不等,则值小的指针往后移,若元素值相等,则创建一个值等于两结点元素的新结点,使用尾插法插入到新的链表中,将两个原表指针后移一位,直到遍历到表尾。
void Get_Comment(LinkList A,LinkList B){
LNode *p=A->next,*q=B->next,*r,*s;
LinkList C=(LinkList)malloc(sizeof(LNode));
r=C; //r始终指向C的尾结点
while(p!=NULL&&q!=NULL){
if(p->data<q->data)
p=p->next;
else if(p->data>q->data)
q=q->next;
else{
s=(LinkList)malloc(sizeof(LNode));
s->data=p->data;
r-next=s;
r=s;
p=p->next;
q=q->next;
}
}
r->next=NULL;
}
//采用归并的思想,设置两个工作指针pa和pb,对两个链表进行归并扫描,只有两个元素的值相等时,将结果放入A链表中,释放另一个。当一个链表遍历完毕后,释放另一个表中剩下的全部结点。时间复杂度O(len1+len2)
LinkList Union(LinkList &la,LinkList &lb){
pa=la->next; //设工作指针分别为pa和pb
pb=lb->next;
pc=la; //结果表中当前合并结点的前驱指针
while(pa&&pb){
if(pa->data==pb->data){
pc->data=pa; //A中结点链接到结果表
pc=pa;
pa=pa->next; //释放B结点
u=pb;
pb=pb->next;
free(u);
}else if(pa->data<pb->data){
u=pa;
pa=pa->next;
free(u);
}else{
u=pb;
pb=pb->next;
free(u);
}
}
while(pa){
u=pa;
pa=pa->next;
free(u);
}
while(pb){
u=pb;
pb=pb->next;
free(u);
}
pc->next=NULL;
free(lb);
return la;
}
//思路:操作从两个链表的第一个结点开始,若对应数据相等,则后移指针;若对应数据不等,则A链表从上次开始比较结点的后继开始,B链表仍从第一个结点开始比较,直到B链表到尾表示匹配成功。若A链表到尾而B链表未到尾表示失败。操作中应记住A链表每次的开始结点。
int Pattern(LinkList A,LinkList B){
LNode *p=A; //本题假定A和B均无头结点
LNode *pre=p; //pre记住每趟比较中A链表的开始结点
LNode *q=B;
while(p&&q){
if(p->data==q->data){
p=p->next;
q=q->next;
}else{
pre=pre->next;
p=pre; //A链表新的开始比较结点
q=B;
}
}
if(q==NULL) //B已经结束,说明B是A的子序列
return 1;
else
return 0;
}
//思路:让p从左向右扫描,q从右向左扫描,直到它们指向同一结点或相邻为止,若它们所指结点值相同,则继续进行下去,否则返回0,带比较全部相等,则返回1.
int Symmetry(DLinkList L){
DNode *p=L->next,*q=L->prior; //两头工作指针
while(p!=q&&q->next!=p){ //p==q表示指向同一结点,结点个数为奇数;q->next==p表示相邻,结点个数为偶数
if(p->data==q->data){
p=p->next;
q=q->prior;
}else
return 0;
}
return 1;
}
//注意:while循环的第二个判断条件不能写成p->next!=q,写成这样会跳出循环,导致链表最中间的两个数不进入while循环。只有全部判断完后,q是在p的左边,所以是q->next!=p才能结束循环。
//先找到两个链表的尾指针,将第一个链表的尾指针与第二个链表的头结点链接起来,第二个链表的尾指针和第一个链表的头结点链接起来。
LinkList Link(LinkList &h1,LinkList &h2){
LNode *p,*q;
p=h1;
while(p->next!=h1) //寻找h1的尾结点
p=p->next;
q=h2;
while(q->next!=h2) //寻找h2的尾结点
q=q->next;
p->next=h2; //将h2链接到h1之后
q->next=h1; //另h2的尾结点指向h1
return h1;
}
//对于循环单链表L,在不空时循环:每循环一个查找一个最小结点(由minp指向最小值结点,minpre指向其前驱结点)并删除它,最后释放头结点。
void Del_All(LinkList &L){
LNode *p,*pre,*minp,*minpre;
while(L->next!=L){ //表不空,进入循环
p=L->next; //p为工作结点,pre指向其前驱
pre=L;
minp=p; //minp指向最小值结点
minpre=pre;
while(p!=L){ //循环一趟,查找最小值结点
if(p->data<minp->data){
minp=p;
minpre=pre;
}
pre=p; //查找下一个结点
p=p->next;
}
printf("%d",minp->data); //输出最小值结点
minpre->next=minp->next; //删除最小值
free(minp);
}
free(L); //删除头结点
}
-
设头指针为L的带有表头结点的非循环双向链表,每个结点还有一个访问频度域freq。在链表被启用前,其值初始化为零。每当链表进行一个Locate(L,x)运算时,令元素值为x的结点中freq值加一,并使结点保持访问频度递减,最近访问的结点排在频度相同的结点之前。编写Locate(L,x)。
//首先在双向链表中查找数据值为x的结点,查到后,将结点从链表上摘下,然后顺着结点的前驱链表查找该结点的插入位置(向前找到一个比它的频度还大的结点,插入位置在该结点之后),并插入到该位置。
DLinkList Locate(DLinkList &L,ElemType x){
DNode *p=L->next,*q; //p为工作指针,q为p的前驱,用于查找插入位置
while(p&&p->data!=x)
p=p->next;
if(!p){
printf("不存在值为x的结点\n");
exit(0);
}else{
p->freq++; //令元素值为x的结点的fred域+1
if(p->next!=NULL)
p->next->pred=p->pred;
p->pred->next=p->next;
q=p->pred;
while(q!=L&&q->fred<=p->fred)
q=q->pred;
p->next=q->next;
q->next->pred=p;
p->pred=q;
q->next=p;
}
return p;
}
//定义两个指针变量p和q,初始时均指向头结点的下一个结点。因为所求为倒数第k个位置,所以p先移动到第k个结点,然后p和q同时向后移动,当p移动到最后一个结点时,q的位置在倒数第k个结点(因为它们的路程长度一样)
typedef int ElemType;
typedef struct LNode{
ElemType data;
struct LNode *link;
}LNode,*LinkList;
int Search_k(LinkList list,int k){
LNode *p=list->link,*q=list->link; //指针p和q指向第一个结点
int count=0;
while(p!=NULL){
if(count<k) //p先移动k个位置
count++;
else
q=q->link; //p和q同时移动
p=p->link;
}
if(count<k) //count小于k表示所求结点不在链表中
return 0;
else{
printf("%d",q->data);
return 1;
}
}
-
假定采用带头结点的单链表保存单词,单两个单词有相同的后缀时,可共享相同的后缀存储空间。设str1和str2分别指向两个单词所在单链表的头结点,设计一个时间上尽可能高效的算法,找出由str1和str2所指向两个链表共同后缀的起始位置。
//思路:先求出两个链表的长度,将链表以表尾对齐,令q和p分别指向str2和str1的头结点,长度较长的先走,直到链表的长度相等。然后判断两个链表对应的位置是否值相等。
int listlen(SNode *head){
int len=0;
while(head->next!=NULL){
len++;
head=head->next;
}
return len;
}
SNode * find_addr(SNode *str1,SNode *str2){
int m,n;
SNode *p,*q;
m=listlen(str1); //求str1的长度
n=listlen(str2); //求str2的长度
for(q=str1;m>n;m--)
p=p->next;
for(q=str2;m<n;n--)
p=p->next;
while(p->next!=NULL&&p->next!=q->next){
p=p->next;
q=q->next;
}
return p->next;
}
//思路:用空间换时间,使用辅助数组记录链表中已出现的数值,初始值均为0.依次扫描链表中的结点,同时检查q的值,若为0则保留该结点,并令q[data]的值+1.否则将该结点从链表删除。
void func(PNODE h,int n){
PNODE p=h,r;
int *q,m;
q=(int *)malloc(sizeof(int)*(n+1));
for(int i=0;i<n+1;i++) //数组元素初值置为0
*(q+i)=0;
while(p->link!=NULL){
m=p->link->data>0?p->link->data:-p->link->data;
if(*(q+m)==0){ //判断该结点的data是否已出现过
*(q+m)=1;
p=p->link;
}else{ //重复出现
r=p->link;
p->link=r->link;
free(r);
}
}
free(q);
}
//思路:设置快慢指针,初始时都指向表头,slow每次走一步,fast每次走两步,若有环,则必相遇。设头结点到环的入口距离为a,环的入口点到相遇点的距离为x,环长是r,相遇时fast绕了n圈。因为fast的速度是slow的二倍,所以得到等式 2(a+x)=a+n*r+x 得到a=n*r-x,因此可设两个指针,一个指向head,一个指向相遇点,同步移动,相遇点即为环的入口点。
LNode* FindLoopStart(LNode *head){
LNode *fast=head,*slow=head;
while(slow!=NULL&&fast->next!=NULL){
slow=slow->next;
fast=fast->next->next;
if(slow==fast) //相遇
break;
}
if(slow==NULL||fast->next==NULL) //没有环
return NULL;
LNode *p1=head,*p2=slow;
while(p1!=p2){ //直到相遇,找到入口点
p1=p1->next;
p2=p2->next;
}
return p1;
}
//思路:结果由L的第一个元素和最后一个元素合并而成,为了方便取最后一个元素,首先将链表后半段逆置。从前后两段依次取一个结点,按要求重排。
void change_list(NODE* h){
NODE *p,*q,*r,*s;
p=q=h;
while(q->next!=NULL){ //寻找中间结点
p=p->next; //p走一步
q=q->next;
if(q->next!=NULL)
q=q->next; //q走两步
}
q=p->next; //p所指结点为中间结点,q为后半段链表的首结点
p->next=NULL;
while(q!=NULL){ //将链表后半段逆置
r=q->next;
q->next=p->next;
p->next=q;
q=r;
}
s=h->next; //s指向前半段的第一个数据结点,即插入点
q=p->next; //q指向后半段的第一个数据结点
p->next=NULL;
while(q!=NULL){
r=q->next; //r指向后半段的下一个结点
q->next=s->next;
s->next=q;
s=q->next; //s指向前半段的下一个插入点
q=r;
}
}