408线性表——链表

对于链表,经常采用的方法有头插法,尾插法,逆置法,归并法,双指针等
 

链表的建立, 插入, 删除

一般情况下插入(除了尾插)和删除至少有一个前驱结点的, 除非直接知道要处理的结点的地址;

遍历链表一般都是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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值