对于链表,经常采用的方法有头插法,尾插法,逆置法,归并法,双指针等
链表的建立, 插入, 删除
一般情况下插入(除了尾插)和删除至少有一个前驱结点的, 除非直接知道要处理的结点的地址;
遍历链表一般都是p = head->next,从第一个元素开始遍历, 插入第i个位置从head开始遍历是想让移动次数和移动到第几个元素在逻辑上相等,移动一次就在第一个元素...移动pos-1从,就是到pos前面。
#include<iostream>
using namespace std;
struct node{
int data;
node* next;
};
//创造链表
//头插法,插到元素前就是头插法(一直在头结点后面擦插入),插在后面就是尾插法,可以用这两种方法创造链表;
node* Create(int arr[], int n) {//尾插法;
node* head = new node;
head->next = NULL;
head->data = 0;
node* pre = head;//创造链表从头结点开始;
//遍历链表从第一个有数结点开始遍历,头结点的哪个0不算链表里面的值的;
for(int i = 0; i < n; i++) {
node* p = new node;
p->data = arr[i];
p->next = NULL;
pre->next = p;
pre = p;
}
return head;
}
//输出链表;
void PrintList(node* head) {
node *p = head->next;//从第一个元素开始,第一个结点是头结点不含元素;
while(p != NULL) {
cout << p->data;
if(p->next != NULL)
cout << " ";
p = p->next;
}
cout << "\n";
}
/*
正常的查找删除都是需要找到前驱结点的,除非给出了删除或插入结点的地址(其实就是相当于帮你遍历一次了)
这样才可以使用后插和后删, 说白了后插和后删只是一个操作;
*/
//在第i个位置插入元素;如在第3个位置插入元素4;
//找到前驱结点后插入;
void InsertNode(node *head, int pos, int x) {//在第i个位置插入元素x, 意思就是插入后第i个位置就是
node *p = head;//从头结点开始遍历;
node *q = new node;
q->data = x;
q->next = NULL;
for(int i = 0; i < pos - 1; i++) {//移动pos-1次,找到前驱节点;
p = p->next;
}
q->next = p->next;
p->next = q;
}
//在p结点前面插入元素q
//重点"前插变后插" ,作用降低时间复杂度
//后插不需要前驱结点,但实际上如果题目没给出p地址,就得自己找p,实际上找p和找p的前驱的时间复杂度是一样的
void Backward_Inser(node *pos, int x) {
node *p = pos;
node *q = new node;
q->next = NULL;
q->data = x;
int temp = p->data;//交换两个结点的值,再把q插到p后面;
p->data = q->data;
q->data = temp;
q->next = p->next;
p->next = q;
}
//删除所有结点值为x的结点;
void DeleteNode(node *head, int x) {
node *p = head->next;
node *pre = head;//删除元素要一前一后,因此p先指向第一个元素, pre指向头指针;
while(p != NULL) {
if(p->data == x) {
pre->next = p->next;
delete p;
p = pre->next;
} else {
pre = p;
p = p->next;
}
}
}
//删除已知地址的链表结点
void DeleteNode(node *pos) {//将后面结点的值覆盖掉p,然后删掉p后面的结点;
node *p = pos;
node *q = p->next;
p->data = q->data;
p->next = q->next;
delete q;
}
void Insert_Delete(node *p, int x) {
node *s = new node;
s->data = x;
s->next = NULL;
//双链表的插入,在p后面插入s
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
//双链表的删除,删除结点p后面的结点s;
p->next = s->next;
s->next->prior = p;//是允许连指的;
delete p;
//删除结点p前面的结点s;
p->prior = s->prior;
s->prior->next = p;
delete s;
}
int main() {
int n = 5;
int arr[n] = {5, 3, 6, 1, 3};
node *head = Create(arr, n);
PrintList(head);
DeleteNode(head, 3);//删除值为3的所有结点
PrintList(head);
node *head2 = Create(arr, n);
InsertNode(head2, 3, 4);//在第3个位置插入4
PrintList(head2);
return 0;
}
1.删除不带头结点的链表中值为x的结点
代码:
void RecursionDeleteX(LinkList &L, Element x){
if(L == NULL)
return;
if(L->data == x){
LNode *p = L;
L = L->next;
free(p);
RecursionDeleteX(L, x);
}else{
RecursionDeleteX(L->next, x);
}
}
//先放着先,等到二叉树的时候应该就能理解了;
2.带头结点的单链表逆序输出
有多种方法,下面写了3种
1.用头插法在原来的表头不断插入原来的元素,使其逆序;
2.在NULL后面不断插入元素,最后左边是NULL右边才是表头,即NULL<-node1<-...<-head;
3.使用递归
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
};
node* Create(int a[], int n) {
node *head = new node;
head->next = NULL;
head->data = 0;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
q->next = NULL;
p->next = q;
p = q;
}
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
//链表不能用类似于顺序表那种通过交换两个数来反转链表,因为表尾指针rear没办法往前走;
/*直接使用将原来结点用头插法在头结点后面疯狂插入结点,头插法得到的元素是倒序的
也可以通过在NULL后面加结点来使得新链表的顺序与原链表的顺序相反
*/
void ReverseList(node *head) {
node *p = head->next;
node *q = head;
head->next = NULL;//头结点的next要初始化为next相当于新链表的第一个结点,这样第一个元素next才不会自己指向自己;
while(p != NULL) {
q = p;//如果是temp = p,然后对p操作,然后再返回到temp,这个是错误的因为temp和p都是指向同一个位置,应该是temp = p->next,temp远离施工区;
p = p->next;
q->next = head->next;
head->next = q;
}
}
//递归的写法,如果是带头结点的,第一次传入的参数应该为第一个元素的地址head->next, 不带头结点直接head就好了
void ReversePrint(node *p) {
if(p->next != NULL)
ReversePrint(p->next);
cout << p->data;
}
/*
void ReverseList(node *head) {
node *rear, *p, *q;
if(head->next != NULL)
rear = head->next;
else
return ;
p = head->next->next;
rear->next = NULL;
while(p != NULL) {
q = p->next;
p->next = rear;
rear = p;
p = q;
}
head->next = rear;
}
*/
//往NUL后面放元素,最后面再放头结点
int main() {
int n = 5;
int a[n] = {1, 2, 2, 4, 2};
node *head = Create(a, n);
Print(head);
//ReversePrint(head->next);
ReverseList(head);
Print(head);
return 0;
}
3.带头结点的链表就地逆置(就地逆置的意思是空间复杂度要为O(1))
1.头插法
2.用三个指针,改变1,2指针之间的指向,即链表指针反转需要3个指针
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
};
node* Create(int a[], int n) {
node *head = new node;
head->next = NULL;
head->data = 0;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
q->next = NULL;
p->next = q;
p = q;
}
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
//链表的就地逆置
//①.头插法(将头结点摘出,相当于新链表头,然后将链表的结点插到头结点后面)
/*void ReverseList(node *head) {
node *p = head->next;
node *q;
head->next = NULL;
while(p != NULL) {
q = p;
p = p->next;
q->next = head->next;
head->next = q;
}
}*/
//②.使用指针反转的方式使得链表倒转,加深对链表的理解
/*
1.三个指针指向最前面的三个结点
2.让第二个结点的next指向第一个结点
3.第一,第二个指针跟着最后一个指针走
4.直到最后最后一个指针指向NULL,最后一个指针再指向头指针即可
第一和第二个指针的结点中间的那条线是最后一条线,处理完这根线后,原头结点指向p(不是r,r在NULL)
细节:原第一个结点会成为最后一个结点,所以遍历前记得把他指向NULL
重点:处理第1,2结点之间的指针,rear的作用是pre和p都是依靠rear走的;
*/
void ReverseList(node *head) {
node *pre = head, *p = head->next, *rear = p->next;
p->next = NULL;//第一次处理,一定不要忘了,否则链表无限长;
while(rear != NULL) {
pre = p;//从后面往前面推, 不要搞错移动的顺序了;
p = rear;
rear = rear->next;
p->next = pre;
}
head->next = p;//头结点再接上去,不要接反了,作为头当然是往下指;
}
int main() {
int n = 5;
int a[n] = {2, 1, 6, 8, 3};
node *head = Create(a, n);
Print(head);
ReverseList(head);
Print(head);
return 0;
}
4.对带头结点的单链表进行排序,使其单调递增
思路:
1.将链表第一个元素后面开始截断,分成链表s1,和s2,s1后面指向NULL,要有一个指针指向s2,否则s2丢失;
2.将s2链表的各个结点插入到链表s1
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
};
node* Create(int a[], int n) {
node *head = new node;
head->next = NULL;
head->data = 0;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
q->next = NULL;
p->next = q;
p = q;
}
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
//对链表进行排序;
void Sort(node *head) {
node *p = head->next;
head->next = NULL;
while(p != NULL) {
node *q = head->next;//每次都从表头开始寻找;
node *pre = head;
while(q != NULL && q->data < p->data) {//找到要插入的位置;
pre = q;
q = q->next;
}
node *temp = p;//插入到第一个大于p->data的结点的前面;
p = p->next;
temp->next = q;
pre->next = temp;
}
}
int main() {
int n = 5;
int a[n] = {5, 4, 3, 2, 1};
node *head = Create(a, n);
Print(head);
Sort(head);
Print(head);
return 0;
}
时间复杂度为O(n^2), 空间复杂度为O(1), 也可以通过数组带存储链表的所有值,再对数组进行排序,再放回到链表里面,复杂度就降到了O(n*logN), 但是空间复杂度变成了O(n),是空间换时间的做法。
还有就是链表排序不用数组时间复杂度最少都要O(n^2)。
5.给定两个带头结点单链表,寻找单链表的第一个公共结点
(因为结点只有一个next领,所以公共结点后面的所有的结点都是重合的,两个链表的形状最后是"Y"形而不会是“X”形状);
方法1.
空间换时间,在每个结构体下,定义一个变量flag初始化为0,遍历第一条链表,将第一条链表的flag全部设置为1,然后再遍历第二条链表,当出现flag == 1的时候说明该结点已经在第一条链表遍历过了,该结点就是公共结点。
代码:
node* FindNum(node *head1, node *head2) {
node *p = head1->next, *q = head2->next;
while(p != NULL) {
p->flag = true;
p = p->next;
}
while(q != NULL && q->flag == false) {
q = q->next;
}
return q;
}
//时间复杂度O(max(len1, len2)), O(cnt),cnt为表1和表2的结点个数;
方法2:
找出那条链表比较长, 比较长的那条先走两个表的长度之差,这样他们剩余的结点长度就都一样了,然后再一起走,当两个指针相等的时候就是公共结点的位置,如果一直走到最后指针都不相同说明没有公共结点
代码:
node* FindSame(node *head1, node *head2) {
node *p = head1->next;
node *q = head2->next;
node *LongList, *ShortList;
int length1 = Length(head1);
int length2 = Length(head2);
int num;//临时变量出了所在的框就被销毁了if的框里面的变量也一样;
if(length1 > length2) {
LongList = p;
ShortList = q;
num = length1 - length2;
} else {
LongList = q;
ShortList = p;
num = length2 - length1;
}
for(int i = 0; i < num; i++)
LongList = LongList->next;
while(LongList != ShortList && LongList != NULL) {//不能写作long->data != short->data;
//因为数值相等也不一定是公共结点'
LongList = LongList->next;
ShortList = ShortList->next;
}
return LongList;
}
时间复杂度是O(len1 + len2);
6.将一个带头结点的单链表按顺序输出,并释放结点所占空间,不能用数组作为辅助存储空间
1.可以用插入排序的方式重新建表,然后输出,再把表中结点一个个删了,时间复杂度应该也是
O(n^2), 空间复杂度为O(1)
2.
分析:
这个方法就是属于找到一个删一个了。
因此就是每遍历一次找到最小结点的前驱,这样就可以删除了, 想用覆盖的方法是行不通的,因为如果最小的一个是最后一个的时候,它后面是NULL,它也不能指向NULL后面的结点。
思路:
假如第一个结点就是最小结点,pre是它的前驱,p作为第二个元素的前驱,用p的后继和pre的后继比,开始遍历,两两比较找出最小值,最后再删掉头结点即可;
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
bool flag = false;
};
node* Create(int a[], int n) {
node *head = new node;
head->next = NULL;
head->data = 0;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
q->next = NULL;
p->next = q;
p = q;
}
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
void PrintMin(node *head) {
while(head->next != NULL) {
//每趟都是从头开始遍历;
node *p =head->next;//p作为要比较的结点的前驱;
node *pre = head;//Pre作为最小结点的前驱,假设第一个结点为最小结点;
while(p->next != NULL) {//遍历除第一个结点外的所有结点,两两比较找出最小值;
if(p->next->data < pre->next->data) {
pre = p;//p是目前最小结点的前驱,因此跑到p这里;
}
p = p->next;
}
node *q = pre->next;
pre->next = q->next;
cout << q->data;
delete q;
}
delete head;//记得把头结点给删了;
}
int main() {
int n = 5, m = 5;
int a[n] = {5, 4, 3, 2, 1};
int b[m] = {2, 4, 5, 7, 9};
node *head = Create(a, n);
Print(head);
PrintMin(head);
//Print(head);
return 0;
}
结论就是链表的前插和删除至少需要一个前驱, 尾插法就不用, 一需要前插和删除,就要想到需要前驱结点
7.将一个带头的单链表分成两个表,一个放为奇数位的结点,一个放偶位的结点,分成两个表后相对顺序不变。(链表一份为2)
分析:相对顺序不变,就想到尾插法
思想:
分成两个表头,两个指针指向这两个表头,一个指针指向原表的第一个元素,遍历原表元素,然后用尾插法插入两表,记得最后结点的next->NULL;
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
bool flag = false;
};
node* Create(int a[], int n) {
node *head = new node;
head->next = NULL;
head->data = 0;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
q->next = NULL;
p->next = q;
p = q;
}
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
node* SeparateList(node *a) {
node *b = new node;//b表的头结点;
b->next = NULL;
b->data = 0;
node *p = a->next;//p用来遍历链表的各个结点,a, b表和原表分开没有了关系,往它两里面插入就好了;
a->next = NULL;
node *aPoint = a, *bPoint = b;//两个指针分别指向两个表头;
int cnt = 1;
while(p != NULL) {
if(cnt % 2 == 0) {//偶数插入b表;
bPoint->next = p;//尾插法;
bPoint = p;
} else {
aPoint->next = p;
aPoint = p;
}
p = p->next;
cnt++;
}
aPoint->next = NULL;
bPoint->next = NULL;
return b;
}
int main() {
int n = 5, m = 5;
int a[n] = {5, 4, 3, 2, 1};
int b[m] = {2, 4, 5, 7, 9};
node *head = Create(a, n);
Print(head);
node *head2 = SeparateList(head);
Print(head);
Print(head2);
return 0;
}
8.将一个有序的单链表中相同的值删除掉,只保留一个(删除链表相同结点)
思路:
q指针始终在p前面,p的隔壁如果相同就删除,不同就说明删完了,要去到隔壁
注意注意注意,这个是有序表,有序表才有这样的规律,如果是无序表的话
如int a[n] = {4, 2, 3, 4, 4};,没办法删除后面两个4,因为对于倒数第2个4来说,在它的右侧只有一个4是相同的,因此最后的输出会是4,2,3,4
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
bool flag = false;
};
node* Create(int a[], int n) {
node *head = new node;
head->next = NULL;
head->data = 0;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
q->next = NULL;
p->next = q;
p = q;
}
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
//有序表删除相同的元素,只留下一个;
/*如果以有一个后继指针,后继指针来扫描链表,如果p作为当前结点作为判断结束条件
那么判断条件是 p->next != NULL, 因此此时后继指针已经来到NULL了,不能对NULL作->data等操作
所以判断条件就是p->next != NULL;
*/
void DeleteSame(node *head) {
node *p = head->next;//因为遇到一个相同的删一个,所以q要一直在p前面,直到隔壁和p不同的移动到隔壁;
if(p == NULL)
return NULL;
node *q = p->next;
while(p->next != NULL) {//到最后一个元素就不用动了,后面已经没有元素要删除了;
q = p->next;//以p为参考,q始终要在p的前面,因为我们需要p和后面的元素进行对比;
if(p->data == q->data) {
p->next = q->next;
delete q;
} else {
p = p->next;
}
}
}
int main() {
int n = 5, m = 5;
int a[n] = {5, 4, 4, 2, 2};
int b[m] = {2, 4, 5, 7, 9};
node *head = Create(a, n);
Print(head);
DeleteSame(head);
Print(head);
return 0;
}
9.两个递增序列合并为一个递减序列(归并+链表逆转)
方法1:
两个递增序列合并成一个递增序列应该想到归并排序
合并后再逆转链表
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
bool flag = false;
};
node* Create(int a[], int n) {
node *head = new node;
head->next = NULL;
head->data = 0;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
q->next = NULL;
p->next = q;
p = q;
}
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
void ReverseList(node *head3) {
node *p = head3->next;
node *pre = head3;
node *rear = head3->next->next;
p->next = NULL;
while(rear != NULL) {//先移动再改变方向就是rear == NULL 的时候结束循环满足条件;
pre = p;
p =rear;
rear = rear->next;
p->next = pre;
}
head3->next = p;
}
node* Merge(node *head1, node *head2) {
node *p = head1->next;
node *q = head2->next;
node *head3 = new node;
node *s = head3;
while(p != NULL && q != NULL) {
if(p->data < q->data) {
s->next = p;
s = p;
p = p->next;
} else {
s->next = q;
s = q;
q = q->next;
}
}
while(p != NULL) {
s->next = p;
s = p;
p = p->next;
}
while(q != NULL) {
s->next = q;
s = q;
q = q->next;
}
s->next = NULL;
ReverseList(head3);
return head3;
}
int main() {
int n = 5, m = 5;
int a[n] = {1, 3, 5 ,7 ,9};
int b[m] = {2, 4, 6, 8, 10};
node *head1 = Create(a, n);
node *head2 = Create(b, m);
Print(head1);
Print(head2);
node *head3 = Merge(head1, head2);
Print(head3);
return 0;
}
方法2:
使用头插法,每次将两表最小的元素头插到第三个表,头插法的顺序是逆序的,因此达到从小到达排序的目的, 即归并再配合头插法的思想即可完成,也不错的,逆序想到头插法。
10.将两个有序链表的公共元素合并为一个表,且该表有序,且不能破坏两表结构(找两表相同元素)
思路:
两指针指向两表,因为元素相同下标不一定相同,因此谁小就往前走,缩小范围(左边界缩小),直到两个指针指向的结点的值相等的时候,用尾插法插入到新表内
本质上就是归并的思想
(如果可以破坏表可以将空间 复杂度降到O(1),这题也可以使用空间换时间的做法,使用hashtable)
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
bool flag = false;
};
node* Create(int a[], int n) {
node *head = new node;
head->next = NULL;
head->data = 0;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
q->next = NULL;
p->next = q;
p = q;
}
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
//注意两表相同元素的
node* SameElemList(node* head1, node* head2) {
node *p = head1->next;
node *q = head2->next;
node *head3 = new node;
node *s = head3;
while(p != NULL && q != NULL) {
if(p->data < q->data) {
p = p->next;
} else if(q->data < p->data){
q = q->next;
} else {
node *temp = new node;//不能破坏链表结构,所以不能直接尾插,要创结点
temp->data = p->data;
s->next = temp;
s = temp;
p = p->next;//相等的时候记得给他们移动,不然卡死在这个相同的点了:
q = q->next;
}
}
s->next = NULL;
return head3;
}
int main() {
int n = 5, m = 5;
int a[n] = {1, 3, 5 ,7 ,10};
int b[m] = {2, 3, 6, 7, 10};
node *head1 = Create(a, n);
node *head2 = Create(b, m);
Print(head1);
Print(head2);
node *head3 = SameElemList(head1, head2);
Print(head3);
return 0;
}
11.问b表是不是a表的连续子序列
思想:
遍历a表,在a表找b表的第一个元素,然后如果一直相等,两表一起动,如果b表遍历完a表还有剩或者刚好也遍历完,则说明b表是a表的连续子序列
连续子序列就是b表是a表的一部分,子集
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
bool flag = false;
};
node* Create(int a[], int n) {
node *head = new node;
head->next = NULL;
head->data = 0;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
q->next = NULL;
p->next = q;
p = q;
}
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
//设第一个表head1为a表,第二个表head2为b表;
node* FindSubsequence(node *head1, node *head2) {
node *p1 = head1->next;
node *p2 = head2->next;
node *p = p1;//当前p1需要判断的结点,因为相等时p1可能会跟着p2一起走,不知道走到哪里去,不对劲了要回来到p继续判断;
while(p1 != NULL && p2 != NULL) {
if(p1->data == p2->data) {
p1 = p1->next;
p2 = p2->next;
} else {
p = p->next;
p1 = p;
p2 = head2->next;//不对劲了p1,p2都要分别回到判断结点和第一个结点,进行下一个结点的判断 ;
}
}
if(p2 == NULL)//判断p2就知道b表有没有遍历完了;
return head2;
else
return NULL;
}
int main() {
int n = 5, m = 3;
int a[n] = {1, 5, 3 ,7 ,10};
int b[m] = {5, 3, 7};
node *head1 = Create(a, n);
node *head2 = Create(b, m);
Print(head1);
Print(head2);
node *head3 = FindSubsequence(head1, head2);
Print(head3);
return 0;
}
这题也可以用寻找公共结点的方法,找到后的步骤和上面的代码就是一样的了,相比之下有点麻烦就是了。
12.将有头结点的循环单链表b接到a后面,结果还得是循环单链表
思路:找a表尾,b表尾,ab头尾相连
记录这道题仅仅是为了提醒循环单链表遍历所有元素的边界不是NULL而是头结点
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
};
node* Create(int a[], int n) {
node *head = new node;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
p->next = q;
p = p->next;
}
p->next = head;
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != head) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
node* LinkList(node *h1, node *h2) {
node *p = h1->next;
while(p->next != h1)//是到表头结束不是到NULL!
p = p->next;//记得p要移动不然死循环;
node *r1 = p;
p = h2->next;
while(p->next != h2)
p = p->next;
node *r2 = p;
r1->next = h2->next;
delete h2;
r2->next = h1;
}
int main() {
int n = 5, m = 3;
int a[n] = {1, 2, 3 ,4 ,5};
int b[m] = {6, 7, 8};
node *head1 = Create(a, n);
node *head2 = Create(b, m);
Print(head1);
Print(head2);
LinkList(head1, head2);
Print(head1);
return 0;
}
13.循环单链表每次输出一个最小值并删除
思路:和前面单链表的思路是一样的,不同的就是判断条件
不循环的输出次数边界是head->next != NULL,循环的边界时head->next != head;
//自己指向自己,头尾都是自己,此时表内没有元素;
不循环遍历一趟找最小值的边界时p->next != NULL,循环的边界是p->next != head;
思路就是,因为删除需要前驱,pre最为最小结点的前驱,p作为判断是否为最小值的结点的前驱
一趟两两比较得出最小,然后删掉pre后面的结点,到了最最后把头结点删除即可
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
};
node* Create(int a[], int n) {
node *head = new node;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
p->next = q;
p = p->next;
}
p->next = head;
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != head) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
//要进行前插或者删除操作,至少要有个前驱;
void PrintMin(node *head) {
while(head->next != head) {
node *pre = head;//假设第一个元素为最小值;
node *p = head->next;//从第二个元素开始遍历;
while(p->next != head) {//注意循环单链表的判断结束条件;
if(p->next->data < pre->next->data) {
pre = p;
}
p = p->next;
}
node *q = pre->next;
pre->next = q->next;
cout << q->data << " ";
delete q;
}
delete head;
}
int main() {
int n = 5, m = 3;
int a[n] = {5, 4, 1, 2, 3};
int b[m] = {6, 7, 8};
node *head1 = Create(a, n);
node *head2 = Create(b, m);
Print(head1);
Print(head2);
PrintMin(head1);
//Print(head1);
return 0;
}
14.双链表的寻找与插入排序
题目:在一个双链表结点按被查找次数递减排序,中寻找某个值,该值的被查找次数增加, 然后更改链表的排序,使得查找次数多的在前面
思路:
找到这个结点,然后让其从链表中断开,然后遍历前驱,找到第一个大于该结点的寻找次数的结点,然后插到它的后面了
主要是有很边界问题,比如说如果结点如果是最后一个结点的时候,NULL是没有前驱的,所以断开该寻找结点的时候,只用前驱指向NULL即可,不用之回来
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
node *prior;
int freg;
};
node* Create(int a[], int n) {
node *head = new node;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
p->next = q;
q->prior = p;
p = q;
}
p->next = NULL;
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
//双链表顺找元素,再排序
node* Locate(node *head, int x) {
node *p = head->next;
node *pre;
while(p != NULL) {
p->freg = 0;
p = p->next;
}
p = head->next;
while(p != NULL && p->data != x)
p = p->next;//找到p的位置;
if(p == NULL)
return NULL;//找不到;
else {
p->freg++;
pre = p->prior;
if(p->next != NULL)//将p拿出来,要考虑p是最后一个的情况,此时后继没有前驱;
p->next->prior = p->prior;//如果是最后一个元素的后继是NULL,NULL当然没有前驱了;
p->prior->next = p->next;
//pre向前找需要插入的地方;
while(pre != head && pre->freg <= p->freg)//最后pre指向第一个大于p的元素,p要插在其后面;
pre = pre->prior;//要等于因为要放到第一个位置;
p->next = pre->next;
if(pre->next != NULL)
pre->next->prior = p;
p->prior = pre;
pre->next = p;
}
return p;
}
int main() {
int n = 5, m = 3;
int a[n] = {5, 4, 3, 2, 1};
int b[m] = {6, 7, 8};
node *head1 = Create(a, n);
node *head2 = Create(b, m);
Print(head1);
Print(head2);
int x = 2;
node *p = Locate(head1, x);
cout << p->data << "\n";
Print(head1);
return 0;
}
15.判断单链表是否有环,并找出其环口值
思路:
先判断是否有环,使用两个指针,一个快指针,一个慢指针,然后遍历链表,如果两个指针没有相遇的说说明没有环,否则就说明有环,因为Lba == La,所以一个从相遇点出发,一个头结点出发,再次相遇的结点就是环口。
如图:
总的来说就是La + Lb == 慢指针走的路程,La + Lb + n*c == 快指针所走的路程
又因为 : 快指针比慢指针速度大一,即慢指针相对快指针是静止的
所以有 : 如果要想让两指针相遇的话,如果两指针相遇,快指针要比慢指针多走一圈,所以n == 1;
所以有 : La + Lb + c == 2 (La + Lb)
所以有 : c = La + Lb
又因为 : c = La + Lba = La + Lb
所以有 : Lba = Lb
即从相遇点出发到环口的距离和从头结点出发到环口的距离相等
代码:
#include<iostream> using namespace std; struct node{ int data; node *next; node *prior; int freg; }; node* Create(int a[], int n) { node *head = new node; node *p = head; node *q; for(int i = 0; i < n; i++) { q = new node; q->data = a[i]; p->next = q; p = q; } q = head; for(int i = 0; i < 3; i++) q = q->next; p->next = q; return head; } void Print(node *head) { node *p = head->next; while(p != NULL) { if(p != head->next) cout << " "; cout << p->data; p = p->next; } cout << "\n"; } int Length(node *head) { node *p = head; int cnt = 1; while(p != NULL) { p = p->next; cnt++; } return cnt; } //单链表判断是否有环,求出其环口值 //设置了一个环口为3的单链表; node* JudgeRing(node *head) { node *p = head; node *q = head; while(p->next != NULL && q != NULL) { p = p->next->next; q = q->next; if(p == q) break; } if(p == NULL || p->next == NULL) return NULL;//说明没有环; p = head;//是从头结点开始出发,不是从head->next出发不要搞错了,lba等于la,应该是包括端点的; while(p != q) { p = p->next; q = q->next; } return p; } int main() { int n = 5, m = 3; int a[n] = {1, 2, 3, 4, 5}; int b[m] = {6, 7, 8}; node *head1 = Create(a, n); //node *head2 = Create(b, m); //Print(head1); //Print(head2); node *p = JudgeRing(head1); cout << "环口值为 : " << p->data; return 0; }
16.只能遍历一遍,找出带头结点的单链表的倒数第k个元素
思想:
用双指针
双指针,两个都从头结点开始,这样移动的次数逻辑上就等于到了第几个元素,等p到了第k个元素后,q再动,这样p,q间是一直相差k-1个元素,当p到底为NULL的时候,q刚好是第k个。
//像这种比较简单的题目,但是又要求高效的算法,只能从遍历次数来找,所以想拿满分只能遍历一次
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
node *prior;
int freg;
};
node* Create(int a[], int n) {
node *head = new node;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
p->next = q;
q->prior = p;
p = q;
}
p->next = NULL;
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
int FindNum(node *head, int k) {
node *p = head;
node *q = head;
while(k-- && p != NULL)
p = p->next;
if(p == NULL && k != 0)
return 0;
while(p != NULL) {
p = p->next;
q = q->next;
}
cout << q->data;
return 1;
}
int main() {
int n = 5, m = 3;
int a[n] = {5, 4, 3, 2, 1};
int b[m] = {6, 7, 8};
node *head1 = Create(a, n);
node *head2 = Create(b, m);
Print(head1);
Print(head2);
int i = 4;
FindNum(head1, i);
return 0;
}
17.删除单链表中元素绝对值相同的结点,只保留出现的第一个
思路:
空间换时间,需要注意的点是,删除的时候p因为指向了下一个元素,已经算是走了一步课,下面就不能出现默认再走一步的操作,这样就相当于走了两步;
比如说:
else {
node *temp = p;
p = p->next;
pre->next = p;
delete temp;
}
pre = p;
p = p->next;//很明显这里p就相当于走了两步,也就是漏了删除后p指向的元素,这个元素没有被遍历到;
18.链表逆置与链表插入
题目:带头结点的单链表L=(a1,a2,a3,..,an),让其变成L=(a1,an,a2,an-1,...);要求空间复杂度为O(1),时间复杂度尽量最优;
思路:
看不懂的题目,可以举例说明,不要轻易放弃,其实就是很简单的题目
假如链表为1,2,3,4,5,6,7,8->NULL,然后让其变成1,8,2,7,3,6,4,5->NULL
由此可以看出,其实也就是分成两个表,分别是表a和表b
也就是a : 1, 2, 3, 4里面插入 b : 8, 7, 6, 5
因此也就是两个步骤,分别是分表和逆置
将表b的值反转然后再插入到表a内
因此思路就是用两指针,一个快指针,一个慢指针,v快 = 2*v慢, 等快指针->next == NULL的时候,慢指针刚好在链表的中间位置,就此分开成两个表,然后b表逆置插回到a表中。
代码:
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
node *prior;
int freg;
};
node* Create(int a[], int n) {
node *head = new node;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
p->next = q;
q->prior = p;
p = q;
}
p->next = NULL;
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
void ReverseInsert(node *head) {
node *f = head;
node *s = head;
while(f != NULL && f->next != NULL) {//前后顺序不要搞错,如果f去到NULL就不能再对其进行取next操作了
//因此要先判断是否为NULL;
f = f->next->next;//f走两步s走一步;
s = s->next;
}
node *q = s->next;
s->next = NULL;
node* head2 = new node;
head2->next = NULL;
node *temp;
while(q != NULL) {//b表逆转;
temp = q->next;
q->next = head2->next;
head2->next = q;
q = temp;
}
node *p = head->next;
q = head2->next;
while(q != NULL) {//b表插入元素到a表;
temp = q->next;
q->next = p->next;
p->next = q;
q = temp;
p = p->next->next;
}
}
int main() {
int n = 7, m = 3;
int a[n] = {1, 2 , 3, 4, 5, 6, 7};
int b[m] = {6, 7, 8};
node *head1 = Create(a, n);
//node *head2 = Create(b, m);
Print(head1);
//Print(head2);
ReverseInsert(head1);
Print(head1);
return 0;
}
给自己加了一题可看可不看,将12345678...,分成13579...,2468....,然后把后面哪个表反转然后插到前面的表的间隙内,成为18365472...,可以用奇数偶数分表也可以通过指针的移动分表
#include<iostream>
using namespace std;
struct node{
int data;
node *next;
node *prior;
int freg;
};
node* Create(int a[], int n) {
node *head = new node;
node *p = head;
for(int i = 0; i < n; i++) {
node *q = new node;
q->data = a[i];
p->next = q;
q->prior = p;
p = q;
}
p->next = NULL;
return head;
}
void Print(node *head) {
node *p = head->next;
while(p != NULL) {
if(p != head->next)
cout << " ";
cout << p->data;
p = p->next;
}
cout << "\n";
}
int Length(node *head) {
node *p = head;
int cnt = 1;
while(p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
//让表1357的间隔插入反过来的2468
node* SeparateList(node *head) {
node *p = head->next;
if(p == NULL && p->next == NULL)
return NULL;
node *q = p->next;
node *head2 = new node;
head2->next = q;
while(p->next != NULL && q->next != NULL) {
node *temp;
temp = p->next->next;
p->next = temp;
p = temp;
temp = q->next->next;
q->next = temp;
q = temp;
}
p->next = NULL;
q->next = NULL;
return head2;
}
void Reverse(node *head) {
node *pre = head;
node *p, *r;
if(pre->next != NULL) {
p = head->next;
if(p->next != NULL)
r = p->next;
} else
return ;
p->next = NULL;
while(r != NULL) {
pre = p;
p = r;
r = r->next;
p->next = pre;
}
head->next = p;
}
void Insert(node *head1, node *head2) {
node *p1 = head1->next;
node *p2 = head2->next;
while(p2 != NULL && p1 != NULL) {
node *t1 = p1->next;
node *t2 = p2->next;
p2->next = p1->next;
p1->next = p2;
p1 = t1;
p2 = t2;
}
}
int main() {
int n = 8, m = 3;
int a[n] = {1, 2 , 3, 4, 5, 6, 7, 8};
int b[m] = {6, 7, 8};
node *head1 = Create(a, n);
//node *head2 = Create(b, m);
Print(head1);
//Print(head2);
node * head2 = SeparateList(head1);
Print(head1);
Print(head2);
Reverse(head2);
Print(head2);
Insert(head1, head2);
Print(head1);
return 0;
}