文章目录
本文只给出关键算法的代码,若要查看能够运行的完整代码,请移步我的资源。
单链表逆置
从尾到头反向输出每个结点的值
题目描述:
设L为不带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。
算法思想:
采用递归的思想,每当访问一个结点时,先递归输出它后面的结点,再输出该结点自身。
实现代码:
//从尾到头反向输出每个结点的值
void RPrint(LinkList L){
if(L->next != NULL){
RPrint(L->next);
}
cout<<L->data<<" ";
}
带头结点的单链表就地逆置
题目描述:
试编写算法将带头结点的单链表就地逆置。所谓就地即辅助空间复杂度为O(1)。
算法思想:
解法一:将头结点摘下,然后从第一个结点开始,依次插入到头结点的后面,即用头插法来建立单链表,直到最后一个结点为止。
解法二:指针反转。定义一个指针p依次线性遍历单链表L,并令指针pre指向p的前驱结点,然后将p结点的指针反转,指向它的前驱结点*pre。最后将头结点指向最后一个结点。
实现代码:
//带头结点的单链表就地逆置,解法一
LinkList Reverse1(LinkList &L){
LNode *p,*r;
p = L->next; //指向第一个结点
L->next = NULL; //摘下头结点,头结点指针域置空
while(p != NULL){
r = p->next; //指向p的后继结点,防止断链
p->next = L->next; //头插法
L->next = p;
p = r;
}
return L;
}
//带头结点的单链表就地逆置,解法二
LinkList Reverse2(LinkList &L){
LNode *pre,*p,*r;
p = L->next; //指向第一个结点
r = p->next; //指向p的后继结点,防止断链
p->next = NULL; //第一个结点的指针域置空
while(r != NULL){
pre = p; //后移一个结点,pre,p,r均后移
p = r;
r = r->next;
p->next = pre;
}
L->next = p; //头指针指向最后一个结点
return L;
}
单链表删除操作
删除不带头结点的单链表L中所有值为x的结点
题目描述:
设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点。
算法思想:
设f(L, x)的功能是删除以L为首结点的单链表中所有值等于x的结点,那么f(L->next, x)的功能就是删除以L->next为首结点的单链表中所有值等于x的结点。那么终止条件为L为空表,若L-data == x,删除*L结点,然后递归调用f(L->next, x),否则,直接调用f(L->next, x)。
实现代码:
//递归算法删除不带头结点的单链表L中所有值为x的结点
void DeleteX(LinkList &L, int x){
LNode *p;
if(L == NULL) return;
if(L->data == x){
p = L; //删除L,并将L指向下一个结点
L = L->next;
free(p);
DeleteX(L->next, x);
}else{
DeleteX(L->next, x);
}
}
删除带头结点的单链表L中所有值为x的结点
题目描述:
在带头结点的单链表L中,删除所有值为x的结点,并释放其空间。假设值为x的结点不唯一。
算法思想:
解法一:用指针p从头到尾遍历单链表,pre指向*p结点的前驱,若p指向的结点值为x,那么就删除,并让p指向下一个结点,否则让pre和p同步后移一个结点。
解法二:利用尾插法建立单链表,用p遍历L的所有结点,当其结点值不为x则将其链接在L之后,否则释放。
实现代码:
//删除带头结点的单链表L中所有值为x的结点,解法一
void DeleteX(LinkList &L, int x){
LNode *p,*pre,*q;
p = L->next; //从第一个结点开始访问
pre = L; //pre指向q的前驱结点
while(p != NULL){
if(p->data == x){
q = p; //删除q
p = p->next; //p指向下一个结点
pre->next = p;
free(q);
}else{ //否则pre和p同步后移
pre = p;
p = p->next;
}
}
}
//删除带头结点的单链表L中所有值为x的结点,解法二
void DeleteX2(LinkList &L, int x){
LNode *p,*q,*r;
p = L->next;
r = L; //r指向尾结点,初始为头结点
while(p != NULL){
if(p->data != x){ //如果结点值不是x那么将其链接到L之后
r->next = p;
r = p;
p = p->next;
}else{ //如果结点值是x那么就将其结点释放
q = p;
p = p->next;
free(q);
}
}
r->next = NULL; //最后尾结点的指针域置空
}
删除递增有序单链表中的重复元素
题目描述:
在一个递增有序的单链表L中,设计算法去掉数值相同的元素,使得表中不再有重复的元素。
算法思想:
由于是有序表,所有相同值域的结点是相邻的。故设置一个工作指针p,用p扫描单链表L,若*p结点的值域等于其后继结点的值域,那么删除其后继结点,否则p后移,直到最后一个结点。
实现代码:
//删除递增有序的单链表中的重复元素
void DeleteSame(LinkList &L){
LNode *p,*q;
p = L->next;
if(p == NULL) return;
while(p->next != NULL){
q = p->next;
if(p->data == q->data){ //当前结点的值域等于其后继结点的值域
p->next = q->next; //删除后继结点
free(q);
}else{
p = p->next; //否则p后移
}
}
}
删除无序单链表中绝对值相等的元素
题目描述:
用单链表保存m个整数,且 |data|<n (n为正整数)。设计一个时间复杂度尽可能高效的算法,对于链表中绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。
算法思想:
算法的核心思想是空间换时间,使用一个辅助数组a[n+1]来记录链表中已经出现的数值,而从只需要队链表进行一道扫描。辅助数组初始值全为0,依次扫描链表中的各个结点,同时检查a[ |data| ],若为0则保留该结点,否则删除该结点。
实现代码:
const int n = 100; //设置n的值
int a[n];
void DeleteSame(LinkList &L){
LNode *p = L->next;
LNode *pre = L,*q; //pre为p的前驱结点,便于删除操作
while(p){
int temp = abs(p->data);
if(a[temp]==0){
a[temp] = 1;
pre = p;
p = p->next;
}else{ //删除p
q = p;
p = p->next;
pre->next = p;
free(q);
}
}
}
删除带头结点的循环单链表中最小值结点
题目描述:
设有一个带头结点的循环单链表,其结点值均为正数。设计算法,找出当前单链表中结点值最小的结点并输出,然后将该结点从中删除,循环上述操作直到链表为空为止,最后删除表头结点。
算法思想:
对于循环单链表L,在不空时循环:每次循环查找一个最小值结点并删除它(由minp指向最小值结点,minpre指向最小值结点的前驱结点),最后释放头结点。为此,应设置一个工作指针p,令指针pre指向*p结点的前驱结点。
实现代码:
void DeleteMin(LinkList &L){
LNode *p,*pre,*minp,*minpre;
while(L->next != L){ //表不空循环
p = L->next; pre = L;
minp = p; minpre = pre; //minp指向最小值结点
while(p != L){ //循环一趟,查找最小值结点
if(p->data < minp->data){
minp = p;
minpre = pre;
}else{
pre = p;
p = p->next;
}
}
cout<<"删除"<<minp->data<<endl;
minpre->next = minp->next; //删除最小值结点
free(minp);
cout<<"删除后单链表L: ";
PrintList(L);
}
free(L); //释放头结点
}
执行结果:
本题的变体形式:
- 按递增次序输出单链表中个结点的数据元素,并释放结点所占存储空间。
- 在带头结点的单链表中删除一个最小值结点。
单链表排序
使单链表L中的元素递增有序
题目描述:
有一个带头结点的单链表L,设计一个算法使其元素递增有序。
算法思想:
采用直接插入排序的算法思想,先构成只含有一个数据结点的单链表,然后依次扫描单链表中剩下的结点*p,直到p == NULL为止,在有序表中通过比较查找插入*p的前驱结点*pre,然后将*p插入到*pre结点之后。
实现代码:
//使单链表L中的元素递增有序
void Sort(LinkList &L){
LNode *p,*pre,*r;
p = L->next;
r = p->next; //r指向p的后继结点,防止断链
p->next = NULL; //构造只含有一个数据结点的单链表
p = r;
while(p != NULL){
r = p->next; //保持p的后继结点
pre = L; //pre指向要插入p的位置的前驱结点
while(pre->next != NULL && pre->next->data<p->data){
pre = pre->next; //指针后移
}
p->next = pre->next; //插入p
pre->next = p;
p = r; //扫描原单链表中剩下的结点
}
}
单链表合并与拆分
将两个递增有序的单链表归并为一个递减有序的单链表
题目描述:
有两个元素值按递增次序排列的单链表,请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表。
算法思想:
两个单链表已经递增有序,故均从第一个结点开始进行比较,将较小的结点插入单链表中,由于要求合并为一个递减次序的排列的单链表,故采用头插法。比较结束后,可能有一个链表非空,将剩余结点按照头插法依次插入新链表即可。
实现代码:
//将两个递增有序单链表A、B归并为一个递减有序单链表
void MergeList(LinkList &La, LinkList &Lb){
LNode *pa,*pb,*r;
pa = La->next;
pb = Lb->next;
La->next = NULL; //将La作为结果链表的头指针,首先置空
while(pa && pb){ //两个链表均不为空时进行比较,选择较小的插入
if(pa->data < pb->data){ //头插法插入结点*pa
r = pa->next; //暂存后继结点指针,防止断链
pa->next = La->next;
La->next = pa;
pa = r;
}else{
r = pb->next; //暂存后继结点指针,防止断链
pb->next = La->next;
La->next = pb;
pb = r;
}
}
if(pa){ //如果A链表非空
pb = pa; //指针pb指向pa所在结点,这样做是为了统一语句,都以指针pb所指向的结点开始,减少代码量
}
while(pb){
r = pb->next;
pb->next = La->next;
La->next = pb;
pb = r;
}
free(Lb); //释放链表B的头结点
}
将一个单链表A拆分为两个单链表A、B
题目描述:
设C = {a1,b1,a2,b2,… ,an,bn},采用带头结点的hc单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A = {a1,a2,… ,an}, B = {bn,… ,b2,b1} 。
算法思想:
设置一个访问序号变量,初值为0,每访问一个结点序号自动加一。由题目可知,链表A中的元素是正序排列的,而链表B中的元素是倒序排列的,故若序号为奇数则利用尾插法将结点插入到链表A中,若序号为偶数则利用头插法将结点插入到链表B中。
实现代码:
//将一个单链表A拆分为两个单链表A、B
LinkList SplitLink(LinkList &A){
LinkList B = (LinkList)malloc(sizeof(LNode));
B->next = NULL; //初始化B
LNode *p,*q,*ra;
p = A->next; //p为工作指针
ra = A; //ra指向A的尾结点
int count = 0;
while(p != NULL){
count++;
if(count%2!=0){ //如果序号是奇数,尾插法插入链表A
ra->next = p;
ra = p;
p = p->next;
}else{ //如果序号是偶数,头插法插入链表B
q = p->next; //q暂存p结点后继指针,防止断链
p->next = B->next;
B->next = p;
p = q;
}
}
ra->next = NULL; //尾结点的指针置空
return B;
}
本题的变体形式:
- 将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素,B表中含有原表中序号为偶数的元素,且保持其相对位置不变。
单链表的查找操作
查找链表中倒数第k个位置上的结点
题目描述:
已知一个带头结点的单链表,该链表只给出了头指针,在不改变链表的前提下,设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点,若查找成功,则输出该结点data域的值,并返回1;否则,只返回0。
算法思想:
定义两个指针变量p和q,初始时均指向头结点的下一个结点,即链表的 第一个结点,p指针沿着链表移动,当p指针移动到第k个结点时,q指针开始与p指针同步移动;当p指针移动到最后一个结点时,q指针所指示的结点即为倒数第k个结点。
实现代码:
//查找链表中倒数第k个位置上的结点
int SearchK(LinkList L, int k){
LNode *p,*q;
p = L->next;
q = L->next;
int count = 0;
while(p){
if(count<k){ //计数,若count<k则只移动p
count++;
p = p->next;
}else{ //让p、q同步移动
p = p->next;
q = q->next;
}
}
if(count<k){ //查找失败返回0
return 0;
}else{
cout<<"倒数第"<<k<<"个结点值为:"<<q->data<<endl;
return 1;
}
}
建立双头链表,相同后缀共享相同存储空间
题目描述:
假设采用带头结点的单链表保存单词,当两个单词有相同后缀时,则可共享相同的后缀存储空间。通常称这样的链表为双头链表。设计一个算法,根据给定的字符串str1和str2,建立一个双头链表,头指针分别为list1和list2.
算法思想:
从两个字符串的最后开始,从后向前构建两个单链表。
实现代码:
//建立双头链表
void Construct(char str1[], char str2[], int m, int n, LinkList &list1, LinkList &list2){
list1 = list2 = NULL;
int i,j;
LNode *p;
for(i=m-1,j=n-1; i>=0 && j>=0; i--,j--){
if(str1[i] == str2[j]){
p = (LNode *)malloc(sizeof(LNode));
p->data = str1[i];
p->next = list1; //list1从后向前移建立链表
list1 = p;
}
else break;
}
list2 = list1; //共享存储空间
for( ; i>=0; i--){ //将str1[]剩下的部分插入到链表list1中
p = (LNode *)malloc(sizeof(LNode));
p->data = str1[i];
p->next = list1;
list1 = p;
}
p = (LinkList)malloc(sizeof(LNode)); //建立list1的头结点
p->next = list1;
list1 = p;
for( ; j>=0; j--){ //将str2[]剩下的部分插入到链表list2中
p = (LNode *)malloc(sizeof(LNode));
p->data = str2[j];
p->next = list2;
list2 = p;
}
p = (LinkList)malloc(sizeof(LNode)); //建立list2的头结点
p->next = list2;
list2 = p;
}
查找两个链表的公共结点
题目描述:
给定两个单链表,编写算法找出两个链表的公共结点。
算法思想:
两个链表有公共结点,即两个链表从某一结点开始,它们的next域都指向同一个结点。由于每个单链表结点只有一个next域,因此从第一个公共结点开始,之后它们所有的结点都是重合的,拓扑形状看起来像Y一样。
故我们可以把问题简化,首先如何判断两个链表有没有公共结点?
若两个链表有至少一个公共结点,那么它们的最后一个结点必然是重合的。因此,分别遍历两个链表到最后一个结点,若两个尾结点是一样的,则说明它们含有公共结点。
那么如何保证在两个链表上遍历同时到达尾结点呢?
首先计算两个链表的表长,假设一个链表比另一个链表长k个结点,那么我们先在长的链表上遍历k个结点,之后再进行同步遍历,就能保证同时到达最后一个结点。
如何求公共结点的开始位置?
由于两个链表从第一个公共结点开始到链表的尾结点,这一个部分是重合的,而上面我们又保证了两个指针遍历同时到达尾结点,因此它们也一定是同时达到第一个公共结点的。故再遍历中,第一个相同的结点就是它们第一个公共的结点。
实现代码:
LNode *SearchCommon(LinkList A, LinkList B){
int len1 = Length(A);
int len2 = Length(B);
int len;
LinkList longlist, shortlist;
if(len1>len2){
longlist = A->next;
shortlist = B->next;
len = len1-len2; //表长之差
}else{
longlist = B->next;
shortlist = A->next;
len = len2-len1;
}
while(len--){
longlist = longlist->next;
}
while(longlist != NULL){
if(longlist == shortlist){
return longlist;
}else{
longlist = longlist->next;
shortlist = shortlist->next;
}
}
return NULL;
}
查找链表A和B的公共元素
题目描述:
设A和B是两个带头结点的单链表,其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C,要求不破坏A和B的结点。
算法思想:
由于A和B链表中的元素是递增有序的,故从前向后比较A和B中的元素:如果A中的元素大于B中的元素,则B中指向该结点的指针后移,否则A中指针后移。若元素值相等,则创建一个新结点其值等于该元素值,使用尾插法插入到链表C中,并将两个原链表指针后移一位,直至其中一个链表遍历到表尾为止。
实现代码:
//查找链表A和B的公共元素
LinkList GetSameElem(LinkList A, LinkList B){
LNode *p = A->next;
LNode *q = B->next;
LinkList C = (LinkList)malloc(sizeof(LNode));
C->next = NULL;
LNode *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{ //值相等
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = p->data;
r->next = s; //尾插法插入到C中
r = s;
p = p->next;
q = q->next;
}
}
r->next = NULL; //尾结点指针置空
return C; //返回公共单链表C
}
判断序列B是否是序列A的连续子序列
题目描述:
两个整数序列A=a1,a2, a3, … ,am和B=b1,b2,b3, … ,bn已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列。
算法思想:
从两个链表的第一个结点开始,若对应数据项等,则后移指针,若对应数据不等,则A链表从上次开始比较结点的后继结点开始,B链表则仍从第一个结点开始比较,直到B链表到表尾表示匹配成功。若A链表到表尾而B链表未到表尾则表示失败。
实现代码:
//判断序列B是否是序列A的连续子序列
int Pattern(LinkList A, LinkList B){
LNode *p,*pre,*q;
p = A->next;
pre = p; //记录A每趟开始比较的位置
q = B->next;
while(p&&q){
if(p->data == q->data){ //结点值相同
p = p->next;
q = q->next;
}else{
pre = pre->next; //上次比较的后继结点
p = pre;
q = B->next; //B从头开始比较
}
}
if(q==NULL) //B已经比较结束
return 1;
else
return 0;
}
判断双链表是否对称
题目描述:
设计一个算法,判断带头结点的循环双链表是否是对称的。
算法思想:
让p从左向右扫描,q从右向左扫描,直到它们指向同一个结点(循环双链表中的结点个数为奇数)或者相邻(循环双链表中的结点个数为偶数)为止,若它们所指的结点值相同,则继续,否则返回0。若全部相等则返回1。
实现代码:
//判断双链表是否对称
int Symmetry(DLinkList L){
DNode *p = L->next;
DNode *q = L->prior;
while(p != q && p->next != q){ //结点数为奇数或偶数时的循环跳出条件
if(p->data == q->data){
p = p->next;
q = q->prior;
}else{
return 0;
}
}
return 1;
}
说明
所有算法的完整代码移步我的资源,持续更新中。。。