第二题
题面:在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。
审题:带头结点 单链表 删除 所有值为x 释放其空间
思路:遍历寻找后继节点为x的结点
算法实现:
void Del_X1(LinkList &L,ElemType x){
LNode *p=L; //p指向头指针
while ( p->next!=NULL ){//用p遍历单链表
LNode *s=p->next; //s为p的后继结点
if ( s->data==x ){ //s值为x 删除s
p->next=s->next;//修改指针域
free(s); //释放其空间
}
else
p=p->next;
}
return ;
}
第三题
题面:设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。
审题:带头结点 单链表 从尾到头 输出每个结点
思路:利用递归
算法实现:
void R_Print(LinkList L){
if ( L->next!=NULL ){
R_Print(L->next);
printf("%d\n",L->next->data);
}
else return ;//递归出口
}
第四题
题面:试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点是唯一的)。
审题:带头结点 单链表 删除 最小值结点 唯一 高效
思路:遍历找到最小值的前驱结点
算法实现:
ElemType Del_Min(LinkList &L){
if ( L->next==NULL ) return -1; //链表为空
LNode *pre=L,*p=pre->next; //p用来遍历,pre为p的前驱
LNode *minpre=pre,*minp=p; //minp为最小值结点,minpre为minp前驱
while ( p!=NULL ){ //遍历单链表
if ( p->data<minp->data) {
minp=p; //更新最小值结点
minpre=pre;
}
p=p->next;
pre=pre->next;
}
ElemType min=minp->data;
minpre->next=minp->next; //删除最小值结点
free(minp); //释放结点
return min;
}
第五题
题面:试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为O(1);
审题:带头结点 单链表 逆置 辅助空间复杂度为O(1)
思路:遍历单链表,将遍历到的结点前插法插入到单链表中,必须要有一个辅助指针记录当前结点的后继结点,防止断链(建议画图理解)
算法实现:
void Reverse(LinkList &L){
LNode *p=L,*q=p->next; //p为工作指针,q为p逆置前的后继结点,保证不断链
L->next=NULL; //保证最后一个结点会指向NULL
while ( q!=NULL ){
p=q; //p后移一位
q=p->next; //q为p的后驱
p->next=L->next; //将p结点插入到头节点之后
L->next=p;
}
}
第六题
题面:有一个带头结点的单链表L,设计一个算法使其元素递增有序。
审题:带头结点 单链表 使其递增有序
思路一:对单链表用插入排序,时间复杂度O(n^2),空间复杂度O(1)
算法实现:
void Sort1(LinkList &L){
LNode *p=L->next,*pre;
LNode *r=p->next; //r为p的后继结点,保证不断链
p->next=NULL; //构造只要一个结点的链表
p=r;
while ( p!=NULL ){
r=p->next; //更新r
pre=L; //pre为插入结点的前驱结点
while ( pre->next!=NULL ){ //遍历寻找合适的插入位置
if( pre->next->data>p->data ) break;
pre=pre->next;
}
p->next=pre->next; //p插入pre后
pre->next=p;
p=r;
}
}
思路二:利用一个辅助数组排序,时间复杂度O(nlog2n),空间复杂度O(n)
算法实现:
void Sort2(LinkList &L){
int fz[100],k=0; //排序辅助数组,先计算单链表长度会跟严谨
LNode *p=L->next; //p指向第一个结点
while ( p!=NULL ){ //遍历单链表
fz[k]=p->data; //记录遍历到的值
k++;
p=p->next;
}
sort(fz,fz+k); //数组排序
p=L; //p指向头节点
for ( int i=0 ; i<k ; i++ ){ //遍历数组,按序尾插到单链表中
LNode* s=(LNode *) malloc(sizeof(LNode));
s->data=fz[i];
s->next=p->next;
p->next=s;
p=s;
}
p->next=NULL; //最后一个结点指向NULL
}
第七题
题面:设在一个带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中所有介于给定的两个值(作为函数参数给出)之间的元素(若存在)。
审题:带头结点 单链表 无序 删除 介于两个值
思路:遍历单链表找到前驱结点后删除
算法实现:
void RangeDel(LinkList &L,ElemType min,ElemType max){
LNode *pre=L,*p=pre->next;
while ( p!=NULL ){ //遍历
if ( p->data>min && p->data <max ){ //符合删除要求
pre->next=p->next;
free(p);
}
pre=pre->next;
p=pre->next;
}
}
第八题
题面:给定两个单链表,编写算法找出两个链表的公共结点。
审题:两个单链表 找出 公共结点(next指向同一结点)
思路:暴力求解时间复杂度为O(n*m);非暴力:若有公共结点则两链表拓扑形状类似 Y ,那么公共结点到尾结点之间的结点都是相同的只需要同时遍历找到第一个next指向同一结点的结点即可,由于两表长度不一定相同,所以长表要先遍历
算法实现:
第九题
题面:给定一个带表头结点的单链表,设head为头指针,结点结构为(data, next),data为整型元素,next为指针,试写出算法:按递增次序输出单链表中各结点的数据元素,并释放结点所占的存储空间(要求:不允许使用数组作为辅助空间)。
审题:带头结点 单链表 递增次序 输出 各结点的数据元素
思路:每次找最小值输出并释放,时间复杂度为O(n2);也可以插入排序后依次删除并释放,时间复杂度也为O(n2);
算法实现:
void Del_Min2(LinkList &L){
while ( L->next!=NULL ){
printf("删除了%d\n",Del_Min(L));
cout<<"操作后"<<endl;PrintList(L);//输出单链表
}
}
第十题
题面:将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变.
审题:带头结点 单链表 分解 A表中含有序号为奇数的元素 B表中含有序号为偶数的元素
思路:遍历单链表,利用一个计数器分解 所有结点都用尾插法
算法实现:
LinkList DisCreat1(LinkList &A){
LinkList B=(LNode *) malloc(sizeof(LNode));//初始化B
B->next=NULL;
int k=1;
LNode *p=A->next; //p用来遍历
LNode *preb=B,*prea=A; //插入结点的前驱;
while ( p!=NULL ){ //遍历
if ( k%2==1 ){ //序号为奇数 尾插法
prea->next=p;
prea=p;
}
else{ //序号为偶数 尾插法
preb->next=p;
preb=p;
}
k++;
p=p->next;
}
prea->next=NULL;
preb->next=NULL;
cout<<"操作后A"<<endl;PrintList(A);//输出单链表
cout<<"操作后B"<<endl;PrintList(B);//输出单链表
return B;
}
第十一题
题面:设C={a1, b1, a2, b2,…, an bm,}为线性表,采用带头结点的hc单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A={a1, a2,…,an},B={bm…, b2, b1}。
审题:带头结点 单链表 拆分
思路:与第10题类似,只是B要用头插法
算法实现:
LinkList DisCreat2(LinkList &A){
LinkList B=(LNode *) malloc(sizeof(LNode));//初始化B
B->next=NULL;
LNode *p=A->next,*q; //p用来遍历,q为p的后继,防止断链
LNode *prea=A; //插入结点的前驱;
while ( p!=NULL ){ //遍历
prea->next=p; //尾插法
prea=p;
p=p->next;
if ( p!=NULL) q=p->next;
p->next=B->next; //头插法
B->next=p;
p=q;
}
prea->next=NULL;
cout<<"操作后A"<<endl;PrintList(A);//输出单链表
cout<<"操作后B"<<endl;PrintList(B);//输出单链表
return B;
}
第十二题
题面:在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,使表中不再有重复的元素,例如(7,10,10,21,30, 42, 42, 42,51,70)将变为(7,10,21,30, 42,51, 70)。
审题:递增有序 带头结点 单链表 去重
思路:遍历删除重复结点即可
算法实现:
void Del_Same(LinkList &L){
LNode *p=L->next,*q; //p用来遍历,q为p的后继结点
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;
}
}
第十三题
题面:假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表
审题:两个 递增有序 带头结点 单链表 归并 递减有序 利用原有结点
思路:利用头插法合并
算法实现:
void MergeList(LinkList &A,LinkList &B){
LNode *a=A->next,*b=B->next;
LNode *r; //记录后继节点,防止断链
A->next=NULL; //A的头节点作为归并后的头节点
while ( a!=NULL && b!=NULL ){
if ( a->data < b->data ){ //优先放入值小的结点
r=a->next;
a->next=A->next;
A->next=a;
a=r;
}
else{
r=b->next;
b->next=A->next;
A->next=b;
b=r;
}
}
if (a!=NULL) b=a; //以上归并可能会有一个链表不为空
while (b){ //不为空的链表全部接到新链表之后
r=b->next;
b->next=A->next;
A->next=b;
b=r;
}
free(b);
}
第十四题
题面:设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C,要求不破坏A、B的结点。
审题:两个 递增有序 带头结点 单链表 公共元素
思路:两指针遍历,每次移动较小元素的指针
算法实现:
LinkList Get_Common(LinkList &A,LinkList &B){
LNode *a=A->next,*b=B->next;
LinkList C=(LNode *) malloc(sizeof(LNode));//初始化新链表C
LNode *c=C;
while ( a!=NULL && b!=NULL ){
if ( a->data>b->data )//每次移动较小元素的指针
b=b->next;
else if( a->data<b->data )
a=a->next;
else{ //公共元素插入C
LNode *s=(LNode *) malloc(sizeof(LNode));
s->data=a->data;
c->next=s;
c=s;
b=b->next;
a=a->next;
}
}
c->next=NULL;
cout<<"操作后"<<endl;PrintList(C);//输出单链表
return C;
}
第十五题
题面:已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A与B的交集,并存放于A链表中
审题:两个 递增有序 带头结点 单链表 交集 放于A
思路:与14题类似,只是不另开一个链表。但王道书代码里还释放了所有不用的结点
算法实现:
void Union(LinkList &A,LinkList &B){
LNode *a=A->next,*b=B->next; //用于遍历的指针
A->next=NULL;
LNode *p=A; //p为插入的前驱节点
while ( a!=NULL && b!=NULL ){
if ( a->data>b->data )
b=b->next;
else if( a->data<b->data )
a=a->next;
else{ //s结点插在p结点后
LNode *s=(LNode *) malloc(sizeof(LNode));
s->data=a->data;
p->next=s;
p=s;
b=b->next;
a=a->next;
}
}
p->next=NULL;
}
第十六题
题面:两个整数序列A=a1,a2, a3,…, am和B=b1, b2, b3,…, bn,已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列。
审题:两个 带头结点 单链表 判断 连续子序列
思路:字符串模式匹配的链式表示形式,kmp时间复杂度O(m+n)
算法实现:
//暴力匹配时间复杂度O(m*n)
bool Pattern(LinkList &A,LinkList &B){
LNode *pre=A; //每次重新匹配的起点
LNode *a=A,*b=B; //用于匹配的指针
while ( pre!=NULL && b!=NULL ){
if( a->data==b->data ){ //当前结点匹配
a=a->next;
b=b->next;
}
else{ //重新匹配
pre=pre->next;
b=B->next;
a=pre;
}
}
if ( b==NULL ) return true;
else return false;
}
第二十一题【2009统考真题】
题面:已知一个带有表头结点的单链表,假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1;否则,只返回0。
审题:带头结点 单链表 查找 倒数第k个 高效
思路:利用两个距离为k的指针遍历查找,时间复杂度O(n+k),空间复杂度O(1)
算法实现:
ElemType Search_k(LinkList list,int k){
LNode *p=list,*q=list;
int count=0;
while ( count!=k && q!=NULL ) { //q先指向第k个结点
q=q->next;
count++;
}
if ( count!=k ) return 0; //查找失败
while ( q!=NULL ){ //pq共同后移
q=q->next;
p=p->next;
}
printf("倒数第%d个结点值为:%d\n",k,p->data);
return 1;
}
第二十二题【2012统考真题】
题面:假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,可共享相同的后缀存储空间,设strl和str2分别指向两个单词所在单链表的头结点,链表结点结构为data next请设计一个时间上尽可能高效的算法,找出由strl和str2所指向两个链表共同后缀的起始位置(如图中字符i所在结点的位置p)。
审题:
思路:与第八题类似
算法实现:符合要求的单链表构造比较麻烦
第二十三题【2015统考真题】
题面:用单链表保存m个整数,结点的结构为[data] [link],且|data|<=n(n为正整数)。现要求设计一个时间复杂度尽可能高效的算法,对于链表中data的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。
审题:带头结点 单链表 保留第一次出现 绝对值相等的结点
思路:利用辅助数组记录已出现的绝对值,时间复杂度O(m),空间复杂度O(n)
算法实现:
void func(LinkList L,int n){
LNode *p=L,*r;
int num[n+5],k;
while(p->next!=NULL){
k=fabs(p->next->data);
if (num[k]==0) {
num[k]=1;//未出现过的绝对值
p=p->next;
}
else{ //现过的绝对值 删除
r=p->next;
p->next=r->next;
free(r);
}
}
free(p);
}
第二十四题
题面:设计一个算法完成以下功能:判断一个链表是否有环,如果有找出环的入口点并返回,否则返回 NULL。
审题:判断 链表 是否有环 找出环的入口点
思路:利用一个快指针fast和一个慢指针slow,fast每次走两步,slow每次走一步,fast和slow会相遇则有环,找环的入口点参考王道书讲解
算法实现:符合要求的单链表构造比较麻烦
第二十五题【2019统考真题】
题面:设线性表L=(4,az,a3,…,a-2,ag-1,a,)采用带头结点的单链表保存,
//请设计一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各结点,到线性表L’=(a1,an,a2,an-1,a3,an-2,…)。
审题:带头结点 单链表 重新排列 空间复杂度为O(1) 时间高效
思路:将后半部分逆置后与前半部分合并,时间复杂度O(n)
算法实现:
void Change_List(LinkList &L){
//找到中间位置 时间复杂度O(n)
LNode *p=L,*q=L;
while( q->next!=NULL ){ //p走一步,q走两步
p=p->next;
q=q->next;
if(q->next!=NULL)q=q->next;
cout<<1<<endl;
}
//后半部分原地逆置 时间复杂度O(n)
LNode *r=p->next,*s;
p->next=NULL;
while(r!=NULL){
s=r;
r=r->next;
s->next=p->next;
p->next=s;
cout<<2<<endl;
}
//合并 时间复杂度O(n)
q=p->next; //q指向后半部分第一个元素
p->next=NULL;
p=L->next; //p指向前半部分第一个元素
while(q!=NULL){//q插在p后
r=q->next; //q的后继 防止断链
q->next=p->next; //插入q
p->next=q;
p=q->next; //p后移
q=r; //q后移
}
return ;
}
测试代码
#include<bits/stdc++.h>
using namespace std;
typedef int ElemType; //线性表元素数据类型
typedef struct LNode{ //定义单链表结点类型
ElemType data; //每个节点存放一个数据元素
struct LNode *next; //指针指向下一元素
}LNode,*LinkList; //LNode * 与 LinkList 等价
void Del_X1(LinkList &L,ElemType x);
void R_Print(LinkList L);
ElemType Del_Min(LinkList &L);
void Reverse(LinkList &L);
void Sort1(LinkList &L);
void Sort2(LinkList &L);
void RangeDel(LinkList &L,ElemType min,ElemType max);
void Del_Min2(LinkList &L);
LinkList DisCreat1(LinkList &A);
LinkList DisCreat2(LinkList &A);
void Del_Same(LinkList &L);
void MergeList(LinkList &A,LinkList &B);
LinkList Get_Common(LinkList &A,LinkList &B);
void Union(LinkList &A,LinkList &B);
bool Pattern(LinkList &A,LinkList &B);
ElemType Search_k(LinkList list,int k);
void func(LinkList L,int n);
void Change_List(LinkList &L);
//初始化一个单链表(带头节点)
bool InitList(LinkList &L){
L = (LNode *) malloc(sizeof(LNode)); //分配一个头节点
if ( L==NULL ) return false; //内存不足 分配失败
L->next = NULL; //头节点之后暂时没有结点
return true;
}
//尾插法建立单链表
LinkList List_TailInsert(LinkList &L){//正向建立单链表
ElemType x;
printf("请输入要插入的数(9999截止):");
scanf("%d",&x);
L=(LNode *) malloc(sizeof(LNode));
LNode *p=L,*s;
while(x!=9999){
s=(LNode *) malloc(sizeof(LNode));
s->data=x;
p->next=s;
p=s;
// printf("请输入要插入的数:");
scanf("%d",&x);
}
p->next=NULL;
return L;
}
//输出链表
void PrintList(LinkList L){
LNode *p; //p指向当前扫描到的结点
int j=0; //p指向第 j 个结点
p=L; //L指向头节点 头节点是第0个结点
while ( p->next!=NULL ){ //后继节点不为空则输出后继节点
p=p->next;
j++;
printf("List[%d]=%d\n",j,p->data);
}
}
void test(){
//初始化单链表
LinkList L1;
InitList(L1);
List_TailInsert(L1);//尾插法建立单链表
cout<<"原单链表L1"<<endl;PrintList(L1);//输出单链表
// LinkList L2;
// InitList(L2);
// List_TailInsert(L2);//尾插法建立单链表
// cout<<"原单链表L2"<<endl;PrintList(L2);//输出单链表
// Del_X1(L1,5);
// R_Print(L1);
// Del_Min(L1);
// Reverse(L1);
// Sort1(L1);
// Sort2(L1);
// RangeDel(L1,3,6);
// Del_Min2(L1);
// DisCreat1(L1);
// DisCreat2(L1);
// Del_Same(L1);
// MergeList(L1,L2);
// Get_Common(L1,L2);
// Union(L1,L2);
// if(Pattern(L1,L2)) printf("B是序列A的连续子序列\n");
// else printf("B不是序列A的连续子序列\n");
// int k=5; Search_k(L1,k);
// func(L1,20);
Change_List(L1);
cout<<"操作后L1"<<endl;PrintList(L1);//输出单链表
return ;
}
int main(){
test();
return 0;
}
//25. 【2019统考真题】设线性表L=(4,az,a3,…,a-2,ag-1,a,)采用带头结点的单链表保存,
//请设计一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各结点,到线性表L'=(a1,an,a2,an-1,a3,an-2,…)。要求:
//1)给出算法的基本设计思想。
//2)根据设计思想,采用C或 C++语言描述算法,关键之处给出注释。
//3)说明你所设计的算法的时间复杂度。
//审题:带头结点 单链表 重新排列 空间复杂度为O(1) 时间高效
//思路:将后半部分逆置后与前半部分合并,时间复杂度O(n)
//输入数据:1 2 3 4 5 6 7 8 9999
void Change_List(LinkList &L){
//找到中间位置 时间复杂度O(n)
LNode *p=L,*q=L;
while( q->next!=NULL ){ //p走一步,q走两步
p=p->next;
q=q->next;
if(q->next!=NULL)q=q->next;
cout<<1<<endl;
}
//后半部分原地逆置 时间复杂度O(n)
LNode *r=p->next,*s;
p->next=NULL;
while(r!=NULL){
s=r;
r=r->next;
s->next=p->next;
p->next=s;
cout<<2<<endl;
}
//合并 时间复杂度O(n)
q=p->next; //q指向后半部分第一个元素
p->next=NULL;
p=L->next; //p指向前半部分第一个元素
//q插在p后
while(q!=NULL){
r=q->next; //q的后继 防止断链
q->next=p->next; //插入q
p->next=q;
p=q->next; //p后移
q=r; //q后移
}
return ;
}
//24.设计一个算法完成以下功能:判断一个链表是否有环,如果有找出环的入口点并返回,否则返回 NULL。
//审题:判断 链表 是否有环 找出环的入口点
//思路:利用一个快指针fast和一个慢指针slow,fast每次走两步,slow每次走一步
//fast和slow会相遇则有环,找环的入口点参考王道书讲解
//输入数据:符合要求的单链表构造比较麻烦
//23.【2015统考真题】用单链表保存m个整数,结点的结构为[data] [link],且|data|<=n(n为正整数)。现要求设计一个时间复杂度尽可能高效的算法,
//对于链表中data的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。
//1)给出算法的基本设计思想。
//2)使用C或C++语言,给出单链表结点的数据类型定义。
//3)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
//4)说明你所设计算法的时间复杂度和空间复杂度。
//审题:带头结点 单链表 保留第一次出现 绝对值相等的结点
//思路:利用辅助数组记录已出现的绝对值,时间复杂度O(m),空间复杂度O(n)
//输入数据:21 -15 -15 -7 15 9999
void func(LinkList L,int n){
LNode *p=L,*r;
int num[n+5],k;
while(p->next!=NULL){
k=fabs(p->next->data);
if (num[k]==0) {
num[k]=1;//未出现过的绝对值
p=p->next;
}
else{ //现过的绝对值 删除
r=p->next;
p->next=r->next;
free(r);
}
}
free(p);
}
//22. 【2012统考真题】假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,可共享相同的后缀存储空间,
//设strl和str2分别指向两个单词所在单链表的头结点,链表结点结构为data next请设计一个时间上尽可能高效的算法,
//找出由strl和str2所指向两个链表共同后缀的起始位置(如图中字符i所在结点的位置p)。要求:
//1)给出算法的基本设计思想。
//2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
//3)说明你所设计算法的时间复杂度。
//审题:
//思路:与第八题类似
//输入数据:符合要求的单链表构造比较麻烦
//21.【2009统考真题】已知一个带有表头结点的单链表,假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,
//查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1;否则,只返回0。要求:
//1)描述算法的基本设计思想。
//2)描述算法的详细实现步骤。
//3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用C、C++或Java语言实现),关键之处请给出简要注释。
//审题:带头结点 单链表 查找 倒数第k个 高效
//思路:利用两个距离为k的指针遍历查找,时间复杂度O(n+k),空间复杂度O(1)
//输入数据:1 2 3 4 5 6 7 8 9 9999
ElemType Search_k(LinkList list,int k){
LNode *p=list,*q=list;
int count=0;
while ( count!=k && q!=NULL ) { //q先指向第k个结点
q=q->next;
count++;
}
if ( count!=k ) return 0; //查找失败
while ( q!=NULL ){ //pq共同后移
q=q->next;
p=p->next;
}
printf("倒数第%d个结点值为:%d\n",k,p->data);
return 1;
}
//16.两个整数序列A=a1,a2, a3,…, am和B=b1, b2, b3,…, bn,已经存入两个单链表中,
//设计一个算法,判断序列B是否是序列A的连续子序列。
//审题:两个 带头结点 单链表 判断 连续子序列
//思路:字符串模式匹配的链式表示形式,
//输入数据:1 2 2 1 2 2 1 3 4 9999 1 2 2 1 3 9999
//暴力匹配时间复杂度O(m*n)
bool Pattern(LinkList &A,LinkList &B){
LNode *pre=A; //每次重新匹配的起点
LNode *a=A,*b=B; //用于匹配的指针
while ( pre!=NULL && b!=NULL ){
if( a->data==b->data ){ //当前结点匹配
a=a->next;
b=b->next;
}
else{ //重新匹配
pre=pre->next;
b=B->next;
a=pre;
}
}
if ( b==NULL ) return true;
else return false;
}
//kmp时间复杂度O(m+n)
//15.已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A与B的交集,并存放于A链表中
//审题:两个 递增有序 带头结点 单链表 交集 放于A
//思路:与14题类似,只是不另开一个链表。但王道书代码里还释放了所有不用的结点
//输入数据:1 3 5 7 9 9999 2 3 4 5 6 7 8 9999
void Union(LinkList &A,LinkList &B){
LNode *a=A->next,*b=B->next; //用于遍历的指针
A->next=NULL;
LNode *p=A; //p为插入的前驱节点
while ( a!=NULL && b!=NULL ){
if ( a->data>b->data )
b=b->next;
else if( a->data<b->data )
a=a->next;
else{ //s结点插在p结点后
LNode *s=(LNode *) malloc(sizeof(LNode));
s->data=a->data;
p->next=s;
p=s;
b=b->next;
a=a->next;
}
}
p->next=NULL;
}
//14.设A和B是两个单链表(带头结点),其中元素递增有序。
//设计一个算法从A和B中的公共元素产生单链表C,要求不破坏A、B的结点。
//审题:两个 递增有序 带头结点 单链表 公共元素
//思路:两指针遍历,每次移动较小元素的指针
//输入数据:1 3 5 7 9 9999 2 3 4 5 6 7 8 9999
LinkList Get_Common(LinkList &A,LinkList &B){
LNode *a=A->next,*b=B->next;
LinkList C=(LNode *) malloc(sizeof(LNode));
LNode *c=C;
while ( a!=NULL && b!=NULL ){
if ( a->data>b->data )
b=b->next;
else if( a->data<b->data )
a=a->next;
else{
LNode *s=(LNode *) malloc(sizeof(LNode));
s->data=a->data;
c->next=s;
c=s;
b=b->next;
a=a->next;
}
}
c->next=NULL;
cout<<"操作后"<<endl;PrintList(C);//输出单链表
return C;
}
//13.假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。
//请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表
//审题:两个 递增有序 带头结点 单链表 归并 递减有序 利用原有结点
//思路:利用头插法合并
//输入数据:1 3 5 7 9 9999 2 4 6 8 9999
void MergeList(LinkList &A,LinkList &B){
LNode *a=A->next,*b=B->next;
LNode *r; //记录后继节点,防止断链
A->next=NULL; //A的头节点作为归并后的头节点
while ( a!=NULL && b!=NULL ){
if ( a->data < b->data ){
r=a->next;
a->next=A->next;
A->next=a;
a=r;
}
else{
r=b->next;
b->next=A->next;
A->next=b;
b=r;
}
}
if (a!=NULL) b=a;
while (b){
r=b->next;
b->next=A->next;
A->next=b;
b=r;
}
free(b);
}
//12.在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,
//使表中不再有重复的元素,例如(7,10,10,21,30, 42, 42, 42,51,70)将变为(7,10,21,30, 42,51, 70)。
//审题:递增有序 带头结点 单链表 去重
//思路:遍历删除重复结点即可
//输入数据:7 10 10 21 30 42 42 42 51 70 9999
void Del_Same(LinkList &L){
LNode *p=L->next,*q; //p用来遍历,q为p的后继结点
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;
}
}
//11.设C={a1, b1, a2, b2,…, an bm,}为线性表,采用带头结点的hc单链表存放,
//设计一个就地算法,将其拆分为两个线性表,使得A={a1, a2,…,an},B={bm…, b2, b1}。
//审题:带头结点 单链表 拆分
//思路:与第10题类似,只是B要用头插法
//输入数据:3 5 1 4 2 6 9999
LinkList DisCreat2(LinkList &A){
LinkList B=(LNode *) malloc(sizeof(LNode));//初始化B
B->next=NULL;
LNode *p=A->next,*q; //p用来遍历,q为p的后继,防止断链
LNode *prea=A; //插入结点的前驱;
while ( p!=NULL ){ //遍历
prea->next=p;
prea=p;
p=p->next;
if ( p!=NULL) q=p->next;
p->next=B->next;
B->next=p;
p=q;
}
prea->next=NULL;
cout<<"操作后A"<<endl;PrintList(A);//输出单链表
cout<<"操作后B"<<endl;PrintList(B);//输出单链表
return B;
}
//10.将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表
//中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变.
//审题:带头结点 单链表 分解 A表中含有序号为奇数的元素 B表中含有序号为偶数的元素
//思路:遍历单链表,利用一个计数器分解 所有结点都用尾插法
//输入数据:3 5 1 4 2 6 9999
LinkList DisCreat1(LinkList &A){
LinkList B=(LNode *) malloc(sizeof(LNode));//初始化B
B->next=NULL;
int k=1;
LNode *p=A->next; //p用来遍历
LNode *preb=B,*prea=A; //插入结点的前驱;
while ( p!=NULL ){ //遍历
if ( k%2==1 ){ //序号为奇数
prea->next=p;
prea=p;
}
else{ //序号为偶数
preb->next=p;
preb=p;
}
k++;
p=p->next;
}
prea->next=NULL;
preb->next=NULL;
cout<<"操作后A"<<endl;PrintList(A);//输出单链表
cout<<"操作后B"<<endl;PrintList(B);//输出单链表
return B;
}
//9.给定一个带表头结点的单链表,设head为头指针,结点结构为(data, next),data为整型元素,next为指针,
//试写出算法:按递增次序输出单链表中各结点的数据元素,并释放结点所占的存储空间(要求:不允许使用数组作为辅助空间)。
//审题:带头结点 单链表 递增次序 输出 各结点的数据元素
//思路:每次找最小值输出并释放,时间复杂度为O(n^2);也可以插入排序后依次删除并释放,时间复杂度相同
//输入数据:3 5 1 4 2 6 9999
void Del_Min2(LinkList &L){
while ( L->next!=NULL ){
printf("删除了%d\n",Del_Min(L));
cout<<"操作后"<<endl;PrintList(L);//输出单链表
}
}
//8.给定两个单链表,编写算法找出两个链表的公共结点。
//审题:两个单链表 找出 公共结点(next指向同一结点)
//思路:暴力求解时间复杂度为O(n*m),
//若有公共结点则两链表拓扑形状类似 Y ,那么公共结点到尾结点之间的结点都是相同的
//只需要同时遍历找到第一个next指向同一结点的结点即可,
//由于两表长度不一定相同,所以长表要先遍历
//输入数据:本体重在算法思想,数据捏造比较麻烦
//7.设在一个带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,
//删除表中所有介于给定的两个值(作为函数参数给出)之间的元素(若存在)。
//审题:带头结点 单链表 无序 删除 介于两个值
//思路:遍历单链表找到后删除
//输入数据:3 5 1 4 2 6 9999
void RangeDel(LinkList &L,ElemType min,ElemType max){
LNode *pre=L,*p=pre->next;
while ( p!=NULL ){ //遍历
if ( p->data>min && p->data <max ){ //符合删除要求
pre->next=p->next;
free(p);
}
pre=pre->next;
p=pre->next;
}
}
//6.有一个带头结点的单链表L,设计一个算法使其元素递增有序。
//审题:带头结点 单链表 使其递增有序
//思路一:对单链表用插入排序,时间复杂度O(n^2),空间复杂度O(1)
//输入数据:3 5 1 4 2 6 9999
void Sort1(LinkList &L){
LNode *p=L->next,*pre;
LNode *r=p->next; //r为p的后继结点,保证不断链
p->next=NULL; //构造只要一个结点的链表
p=r;
while ( p!=NULL ){
r=p->next; //更新r
pre=L; //pre为插入结点的前驱结点
while ( pre->next!=NULL ){ //遍历寻找合适的插入位置
if( pre->next->data>p->data ) break;
pre=pre->next;
}
p->next=pre->next; //p插入pre后
pre->next=p;
p=r;
}
}
//思路二:利用一个辅助数组排序,时间复杂度O(nlog2n),空间复杂度O(n)
void Sort2(LinkList &L){
int fz[100],k=0; //排序辅助数组,先计算单链表长度会跟严谨
LNode *p=L->next; //p指向第一个结点
while ( p!=NULL ){ //遍历单链表
fz[k]=p->data; //记录遍历到的值
k++;
p=p->next;
}
sort(fz,fz+k); //数组排序
p=L; //p指向头节点
for ( int i=0 ; i<k ; i++ ){ //遍历数组,按序尾插到单链表中
LNode* s=(LNode *) malloc(sizeof(LNode));
s->data=fz[i];
s->next=p->next;
p->next=s;
p=s;
}
p->next=NULL; //最后一个结点指向NULL
}
//***5.试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为O(1);
//审题:带头结点 单链表 逆置 辅助空间复杂度为O(1)
//思路:遍历单链表,将遍历到的结点前插法插入到单链表中,
// 必须要有一个辅助指针记录当前结点的后继结点(建议画图理解)
//输入数据:1 2 3 4 5 6 9999
void Reverse(LinkList &L){
LNode *p=L,*q=p->next; //p为工作指针,q为p逆置前的后继结点,保证不断链
L->next=NULL; //保证最后一个结点会指向NULL
while ( q!=NULL ){
p=q; //p后移一位
q=p->next; //q为p的后驱
p->next=L->next; //将p结点插入到头节点之后
L->next=p;
}
}
//4、试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点是唯一的)。
//审题:带头结点 单链表 删除 最小值结点 唯一 高效
//思路:遍历找到最小值的前驱结点
//输入数据: 3 5 1 4 2 6 9999
ElemType Del_Min(LinkList &L){
if ( L->next==NULL ) return -1; //链表为空
LNode *pre=L,*p=pre->next; //p用来遍历,pre为p的前驱
LNode *minpre=pre,*minp=p; //minp为最小值结点,minpre为minp前驱
while ( p!=NULL ){ //遍历单链表
if ( p->data<minp->data) {
minp=p; //更新最小值结点
minpre=pre;
}
p=p->next;
pre=pre->next;
}
ElemType min=minp->data;
minpre->next=minp->next; //删除最小值结点
free(minp); //释放结点
return min;
}
//3.设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。
//审题:带头结点 单链表 从尾到头 输出每个结点
//思路:利用递归
//输入数据:1 2 3 4 5 6 9999
void R_Print(LinkList L){
if ( L->next!=NULL ){
R_Print(L->next);
printf("%d\n",L->next->data);
}
else return ;
}
//2.在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,
//假设值为x的结点不唯一,试编写算法以实现上述操作。
//审题:带头结点 单链表 删除 所有值为x 释放其空间
//思路:遍历寻找后继节点为x的结点
//输入数据:5 3 5 6 7 5 9999
void Del_X1(LinkList &L,ElemType x){
LNode *p=L; //p指向头指针
while ( p->next!=NULL ){//用p遍历单链表
LNode *s=p->next; //s为p的后继结点
if ( s->data==x ){ //s值为x 删除s
p->next=s->next;//修改指针域
free(s); //释放其空间
}
else
p=p->next;
}
return ;
}