线性表的链式表示,来自王道,部分思路与答案不同。
如有错误,欢迎指正!
/*
结构体声明
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
*/
// 1. 设计一个递归算法,删除不带头结点的单链表L中的所有值为x的结点
// 算法思想:传递的为上一结点的指针域,挺有意思的一个算法
// 时间复杂度:O(n),空间复杂度O(n)
void deleteAllx(LinkList &L, ElemType x)
{
LNode* p;
if(L == null)
return;
if(L->data == x)
{
p = L;
L = L->next; //为上一结点的指针域
free(p); // C++ STL 用于释放malloc函数所开辟的空间,与malloc函数配对使用
deleteAllx(L, x);
}
else
deleteAllx(L->next, x);
}
//2. 在带头节点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一
// 算法思想: 两指针,一指针记录前结点,一指针记录当前结点
//时间复杂度:O(n),空间复杂度O(1)
void deleteAllx2(LinkList &L, ElemType x)
{
LNode* p1 = L;
LNode* p2 = L->next;
LNode* tem;
while(p2!= null)
{
if(p2->data == x)
{
tem = p2;
p1->next = p1->next->next;
p2 = p1->next;
free(tem);
}else
{
p2 = p2->next;
p1 = p1->next;
}
}
}
// 3. 设L为带头节点的单链表,编写算法实现从尾到头反向输出每个结点的值
//算法思想: 第一想法是正向输出取反,但这样空间复杂度为O(n)
//参考答案:利用递归栈,妙!虽然空间复杂度还是O(n),但是代码简单,时间复杂度O(n)
void reverseOutput(LinkList &L)
{
if(L->next == null)
{
cout << L->data << " ";
return;
}
if(L->next != null)
{
reverseOutput(L->next);
cout << L->data <<" ";
return;
}
}
// 4. 在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点唯一)
// 算法思想:提供一个指针记录最小值结点的前驱结点,遍历(逃不过遍历)
// 时间复杂度:O(n) 空间复杂度:O(1)
void deleteMin(LinkList &L)
{
LNode* p1 = L;// 记录前驱结点
LNode* p2 = L->next;// 记录目前结点
LNode* tem;
if(p2 == null)
return;
ElemType x = p2->data;// 记录当前最小值
while(p2->next != null)
{
if(p2->next->data < x)
{
p1 = p2;
p2 = p2->next;
x = p2->data;
}else{
p2 = p2->next;
}
}
tem = p1->next;
p1->next = p1->next->next;
free(tem);
return;
}
// 试编写算法将带头结点的单链表就地逆置,即辅助空间复杂度为O(1)
// 算法思想:提供一指针指向当前结点,遍历链表;再提供一指针指向下一结点
// 从第一个结点开始,下一结点的next指向前结点。
// 两结点各自往前移动一个结点,直到遍历结束
// 时间复杂度:O(n),空间复杂度O(1)
void reverseLink(LinkList &L)
{
LNode* p1 = L;
LNode* p2 = L->next;
LNode* p3;
L->next = null;// 断开,只留一个结点
if(p2 == null)
return;
if(p2->next == null) // 只有两个结点
{
p2->next = p1;
return;
}
while(p2->next != null) // 有两个以上结点
{
p3 = p2->next;
p2->next = p1;
p1 = p2;
p2 = p3;
}
return;
}
// 有一个带头结点的单链表L,设计一个算法使其元素递增有序
// 算法思想:是哪个大聪明想用链表来排序呀
/*
如果用选择排序,需要提供一指针记录最小值的前驱结点,再提供一指针
保存交换位置,再提供一指针记录头结点,时间复杂度为O(n^2)
如果使用冒泡排序,同样需要三个指针,两个当前比较节点的指针,一个前结点
的指针或者前节点next域指针,这样操作会简单一点,时间复杂度为O(n^2)
参考答案给出的方法是插入排序,时间复杂度仍然为O(n^2),同样利用了三个指针。
当然可以使用快排,但我还没掌握,这里使用插入排序
*/
void Sort(LineList &L)
{
LNode* p = L->next; //1
LNode* pre;
LNode* r = p->next; //2
p->next = null;// 只保留头结点和第一个结点,断掉了最初的链表
p = r;// 从第二个结点开始 2
while(p != null)
{
r = p->next;// 3
pre = L;// head
while(pre->next != null && pre->next->data < p->data)
{
pre = pre->next;
}
p->next = pre->next;
pre->next = p;
p = r;
}
}
/**
设在一个带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中
所有介于给定的两个值(作为函数参数给出)之间的元素的元素(若存在)
时间复杂度:O(n),空间复杂度:O(1)
算法思想:
遍历,遇见就删除
**/
void deleteBetween(LinkList &L, int m ,int n)
{
LNode* p1 = L;
LNode* p2 = L->next;
LNode* tem;
if(p2 == null)
return;
while(p2 != null)
{
if(p2->data>m && p2->data<n)
{
tem = p2;
p1->next = p1->next->next;
delete tem;
p2 = p1->next;
}
}
}
/**
给定两个单链表,编写算法找出两个链表的公共结点
算法思想:这个有意思,有时间复杂度为O(m+n)的解法,
利用了公共结点之后的结点必定相同,需要辅助函数判断
链表的长度
时间复杂度:O(m+n) 空间复杂度:O(1)
**/
int getLength(LinkList L1)
{
int c = 0;
while(L1!=null)
{
c++;
L1 = L1->next;
}
return c;
}
LinkList findCommon(LinkList L1, LinkList L2)
{
int len1 = getLength(L1);
int len2 = getLength(L2);
int dist;
LinkList longL,shortL;
if(len1 > len2)
{
longL = L1;
shortL = L2;
dist = len1 - len2;
}else{
longL = L2;
shortL = L1;
dist = len2 - len1;
}
while(dist--)
longL = longL->next;
while(longL != null)
{
if(longL == shortL)
return longL;
else
{
longL = longL->next;
shortL = shortL->next;
}
}
return null;
}
/**
给定一个带头结点的单链表,按递增次序输出单链表中各数据元素
并释放结点所占的存储空间
算法思想:上面使元素递增有序的算法加个输出,最后删除
时间复杂度:O(n^2)空间复杂度,O(1)
**/
void deleteandSortedOutput(LinkList &L, ostream &out)
{
Sort(L);
LNode* tem;
LNode* p1 = L->next;
while(p1!=null)
{
out << L->data <<" ";
tem = p1;
p1 = p1->next;
delete tem;
}
delete L;
}
/**
将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有
表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对
顺序不变。
算法思想:创建头结点+删除结点+保留被删除的节点并连接到新链表
时间复杂度:O(n) 空间复杂度:O(1)
**/
LinkList oddAndEven(LinkList &A)
{
LinkList B = (LinkList)malloc(sizeof(LNode));
B->next = null;
LNode* pre = A;
LNode* p1 = A->next;
LNode* p2 = B;
int c = 1;
while(p1 != null)
{
if(c%2 != 0)
{
pre = p1;
p1 = p1->next;
c++;
}else
{
p2->next = p1;// B链表与偶数节点相连
p2 = p2->next;
pre->next = p1->next;// A链表的偶数节点的前驱指向偶数节点的后驱
p1 = p1->next;// p1节点前移
p2->next = null;// A链表该偶数节点的next域断掉
c++;
}
}
return B;
}
/**
C={a1,b1,a2,b2,..,an,bn}拆分成两个不同的链表A = {a1,a2,a3,..,an}和
B={bn,bn-1,....,b2,b1}
算法思想:和上一题的不同是B上一题使用尾插,这里使用头插
刚看到题的思路是直接逆转链表B
时间复杂度:O(n) 空间复杂度:O(1)
**/
LinkList oddAndReverseEven(LinkList &A)
{
LinkList B = (LinkList)malloc(sizeof(LNode));
B->next = null;
LNode* pre = A;
LNode* p1 = A->next;
LNode* p3;
int c = 1;
while(p1 != null)
{
if(c%2 != 0)
{
pre = p1;
p1 = p1->next;
c++;
}else
{
B->next = p1;// B链表与偶数节点相连
pre->next = p1->next;// A链表的偶数节点的前驱指向偶数节点的后驱
p1 = p1->next;// p1节点前移
B->next->next = p3;//头插
p3 = B->next
c++;
}
}
return B;
}
/**
假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写
算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用
原来两个单链表的结点存放归并的单链表
算法思想:
有序链表的合并
定义一不放数据的头结点,最后删除
每个链表定义一个遍历节点,一个遍历节点的前驱节点
就和公式化线性表的合并流程一样开始合并
时间复杂度:O(n) 空间复杂度O(1)
**/
LinkList mergeLinkList(LinkList &L1, LinkList &L2)
{
LinkList head = (LinkList)malloc(sizeof(LNode));
LNode* r = head;
LNode* p1 = L1;
LNode* p2 = L2;
LNode* temp;
while(p1 != null && p2 != null)
{
if(p1->data > p2->data)
{
r->next = p2;// 新链与旧链链接
r = r->next;// 新链缝合节点往前一节点
p2 = p2->next;// 旧链遍历节点往前一节点
r->next = null;// 新链最后一节点的next域为null
}else
{
r->next = p1;
r = r->next;
p1 = p1->next;
r->next = null;
}
}
if(p1 != null) // 把最后一段没有比较过的连上
r->next = p1;
else
r->next = p2;
temp = head; // 删除辅助用的头结点
head = head->next;
delete temp;
return head;
}
/**
设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共
元素产生单链表C,要求不破坏A、B的节点
算法思想: 重复创建新节点,取元素直接赋值
和合并流程基本一样,不一样的是我们这次只取data相等的节点
时间复杂度:O(n) 空间复杂度:O(1)
**/
LinkList mergeLinkListData(LinkList &L1, LinkList &L2)
{
LNode* head = (LinkList)malloc(sizeof(LNode));
LNode* r = head;
LNode* p1 = L1->next;
LNode* p2 = L2->next;
while(p1 != null && p2 != null )
{
if(p1->data < p2->data)
{
p1 = p1->next;
}else if(p1->data > p2->data)
{
p2 = p2->next;
}else
{
LNode* temp = (LinkList)malloc(sizeof(LNode));
temp->data = p1->data;
r->next = temp;
r = r->next;
p1 = p1->next;
p2 = p2->next;
}
}
return head;
}
/**
已知两个链表A和B分别表示两个集合,其元素递增排序。编制函数,求A和B
的交集,并存放于A链表中
算法思想:交集和公共元素不是一个意思吗??? 区别就是一个是链表C,一个是
原链表A,不用申请新空间,直接在A里delete
时间复杂度:O(n) 空间复杂度:O(1)
**/
void mergeLinkList2(LinkList &L1, LinkList &L2)
{
LNode* p1 = L1;
LNode* pre = (LinkList)malloc(sizeof(LNode));
pre->next = p1;
LNode* p2 = L2;
while(p1 != null && p2 != null)
{
if(p1->data < p2->data)
{
pre->next = pre->next->next;
pre = p1;
delete p1;
p1 = pre->next;
}else if(p1->data > p2->data)
{
p2 = p2->next;
}else
{
pre = p1;
p1 = p1->next;
}
}
}
/**
两个整数序列A = a1,a2,a3,..,an和B = b1,b2,b3,,=..,bn已经存入两个
单链表中,设计一个算法,判断序列B是不是序列A的连续子序列
算法思想:如果n>m,返回false
取出B中的b1,与序列A逐个比对,若存在ak = b1 (k <= m-n+1)
且ak的后续元素与B相同,返回true,否则返回false
时间复杂度:O(n),n为序列A长度 空间复杂度:O(1)
**/
bool contiSequence(LinkList &L1, LinkList &L2)
{
int m = getLength(L1);
int n = getLength(L2);
LNode* p1 = L1;
LNode* p2 = L2;
if(m > n)
return false;
for(int i = 0; i<=m-n; i++)
{
if(p1->data != p2->data)
{
p1 = p1->next;
}
if(p1->data == p2->data)
{
for(int j = 1; j<n; j++)
{
p1 = p1->next;
p2 = p2->next;
if(p1->data != p2->data)
{
return false;
}
}
return true;
}
}
return false;
}
/**
设计一个算法用于判断带头结点的循环双链表是否对称
设计思想:
第一反应是用栈比较好,话说奇数个节点算不算对称
不过既然在链表这一章应该有链表可以和栈相媲美的算法。
提供头尾两个指针,因为是循环双链表,所以找到尾指针的
时间复杂度为O(1)。头指针往右,尾指针往左;当指针的值
每次都相等且遍历至两指针指向的节点相同(奇数)或next
域指向另一指针时返回true
时间复杂度:O(n) 空间复杂度:O(1)
**/
bool isSymmetry(LinkList &L)
{
LNode* head = L->next;
LNode* tail = L->prior;
while(head != tail && head->next != tail)
{
if(head->data == tail->data)
{
head = head->next;
tail = tail->prior;
}else{
return false;
}
}
return true;
}
/**
有两个循环单链表,链表头指针分别为h1和h2,编写一个函数
将链表h2链接到链表h1后,要求链接后的链表仍保持循环链表
形式
算法思想:首先找到两链表的尾指针,之后连接
时间复杂度:O(n) 空间复杂度:O(1)
**/
void connectLinkList(LinkList &L1, LinkList &L2)
{
LNode* p1 = L1;
LNode* p2 = L2;
LNode* t1;
LNode* t2;
while(p1->next != L1)
{
p1 = p1->next;
}
t1 = p1;
while(p2->next != L2)
{
p2 = p2->next;
}
t2 = p2;
p1 = L1;
p2 = L2;
t1->next = p2;
t2->next = p1;
}
/**
设有一个带头节点的循环单链表,其节点值均为正整数。设计一个算法,反复
找出单链中最小的元素并输出,然后将该节点从中删除,直到但链表为空为止
,再删除表头节点
算法思想:提供一个指针,从头节点开始反复遍历,再提供一个指针保存最小
值,每当回到头指针时,删除最小值节点,直到链表只剩头结点,删除头节点
时间复杂度:O(n^2) 空间复杂度:O(1)
**/
void outputMin(LinkList &L)
{
LNode* p1 = L->next;
LNode* m = L->next;
LNode* pre = L;
LNode* pre2 = L;
while(L->next != null)
{
while(p1 != L)
{
if(p1->data < m->data)
{
pre2 = pre;
m = p1;
}
pre = p1;
p1 = p1->next;
}
cout << m->data << " ";
pre2->next = pre2->next->next;
delete m;
}
delete L;
}
/**
设头指针为L的带有表头结点的非循环双链表,其每个节点还有一个
访问频度域freq。在链表被启用前,初始化为0.每当在链表中进行一
次Locate(L, x)运算时,令元素值为x的结点中的freq域的值增1,并
使此链表中结点保持按访问频度递减的顺序排列,同时最新访问的结点
排在频度相同的结点前面,以便使频繁访问的结点总是靠近表头。试编写
符合上述要求的Locate(L,x)运算的算法,该运算为函数过程,返回找到节点
的地址,类型为指针类型
算法思想:以链表已经排好顺序为前提,链表中的一元素值为x的freq域的值增
1便要调整结点顺序使之按频度递减排序,且相同频域中该结点在最前面。因为
返回值为节点的地址,故应该不考虑多个节点元素值都为x的情况.
涉及双链表的插入和删除操作。
时间复杂度: 空间复杂度:O(1)
**/
LinkList Locate(LinkList &L, ElemType x)
{
LNode* p1 = L->next;
LNode* p2;
LNode* p3;
while(p1->data != x && p1 != null)
{
p1 = p1->next;
}
if(p1 == null)
throw "no element's data equals x.";
p1->freq = p1->freq + 1;
p2 =p1;
while(p2->freq->data != p1->freq + 1)
{
p2 = p2->next;
}
p3 = p2->next;
// 到现在p1是要寻找的元素,p2是p1要插入位置的前驱元素,p3是要插入位置的后驱元素。
p1->pred->next = p1->next;// p1的前驱和后驱相互链接
p1->next->pred = p1->pred;
p2->next = p1;// p1的前驱与p1连接
p1->pred = p2;
p1->next = p3;// p1的后驱与p1连接
p3->pred = p1;
return p1;
}
/**
已知一个带表头结点的单链表,在不改变链表的前提下,请设计一个
尽可能高效的算法,查找链表中倒数第K个位置上的节点(k为正整数)
。若查找成功,输出该节点的数据值,否则,只返回0.
算法思想:我只想到了遍历两遍的算法,参考答案的思路为提供两个指针,
一个指针移动k个位置,然后两指针同时移动,当前面的指针移动到尾部时
,后一指针位置即是我们要找的节点,只需要遍历一遍。
时间复杂度:只遍历一遍,空间复杂度:O(1)
**/
bool findk(LinkList &L, int k)
{
LNode* p1 = L;
LNode* p2 = L;
int c;
for(c = 0; c <= k; c++)
{
p1 = p1->next;
if(p1 == null)
return false;
}
while(p1->next != null)
{
p1 = p1->next;
p2 = p2->next;
}
cout << p2->data <<" ";
return true;
}
/**
找出两个不等长带头结点单链表的共同后缀(即节点的位置相同)
和第八个太像了。速通一遍,当做复习
算法思想:先对齐尾部,同时遍历,当遍历到同一节点便是后缀起始位置
时间复杂度:O(m+n)空间复杂度:O(1)
**/
LinkList commonSuffix(LinkList &L1, LinkList &L2)
{
int m = getLength(L1);
int n = getLength(L2);
LNode* shortL;
LNode* longL;
int dis;
if(m > n)
{
dis = m-n;
shortL = L2;
longL = L1;
}else
{
dis = n-m;
shortL = L1;
longL = L2;
}
while(dis--)
longL = longL->next;
while(longL->data != shortL->data && shortL != null)
{
longL = longL->next;
shortL = shortL->next;
}
return shortL;
}
/**
单链表保存m个整数,对于给定整数x,删除所有数据值为x或(-x)的结点
算法思想:和第二题很像,速通速通
时间复杂度:O(n)空间复杂度:O(1)
**/
void deleteXAndAbsx(LinkList &L, int x)
{
int ax = -x;
LNode* pre = L;
LNode* p = L->next;
while(p != null)
{
if(p->data == x || p->data == ax)
{
pre->next = pre->next->next;
delete p;
p = pre->next;
}else
{
pre = p;
p = p->next;
}
}
}
/**
设计一个算法完成以下功能,判断一个链表是否有环,如果有,找出环的入口点
并返回,否则返回NULL
算法思想:我想到的就是指针的指针,估计很难想到参考答案的解法
设置一个快指针和慢指针,慢指针每次走一步,快指针每次走两步。如果有环,慢指针
一定比快指针晚进入环,并在若干步后两指针在环上重合,可以判断一个链表是否有环。
之后是环的入口的问题,设头结点到入口的距离为a,相遇点到入口的距离为x,环长为r
有等式2(a+x) = a+n*r+x成立
等式左边是快指针的“路程”,右边是慢指针的“路程”。相遇时等式成立,
化简则有a+x = n*r;即a+x正好等于在入口处的结点旋转n圈回到入口处。
如果头结点和入口处的节点同时移动,一次走一步,走a+x步,则会在入口处相遇。
但是我们不知道入口处的节点在哪
所以换一种形式,我们现在知道相遇点x,相当于入口处的节点先走了x步。头节点和
相遇节点同时走a步,便可在入口处相遇。
时间复杂度:O(n) 空间复杂度:O(1)
**/
LinkList FindLoopStart(LinkList &L)
{
LNode* fast = L;
LNode* slow = L;
LNode* p1 = L;
LNode* p2;
while(fast != null)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
break;
}
if(fast == null)
return null;
p2 = slow;
while(p1 != p2)
{
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
/**
设线性表L = (a1,a2,a3,...,an-2,an-1,an)采用带头结点的单链表保存,请设计
一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各节点
得到线性表L` = (a1,an,a2,an-1,a3,an-2,...)
算法思想:
最初想法:
提供一个尾节点和头结点指针,头结点指针往右遍历,尾节点指针往左
遍历,同时删除尾节点,插入到要求的位置。
当头结点指针的next域为尾节点指针时,算法完成。
但是这不是双向链表,无法从后往前读,所以应该翻转后半段元素指针指向。
时间复杂度:O(n) 空间复杂度:O(1)
**/
void transformList(LinkList &L)
{
int n = getLength(L);
int m = n/2+1;
LNode* p1 = L->next;
LNode* p2 = L->next;
LNode* tail;
LNode* p3;
while(m--)// 找逆置那一部分的头结点
{
p1 = p1->next;
}
reverseLink(p1); // 之前写的逆置链表方法
while(tail->next != null)// 寻找尾节点
{
tail = tail->next;
}
while(p2->next != tail)
{
p3 = tail;
tail = tail->next; // 注意此时是往左移动
p3->next = p2->next;
p2->next = p3;
p2 = p3->next;
}
}