王道408考研课后习题---线性表(全二十五题)

单链表结构的定义

typedef struct LNode{
	int data;
	struct LNode *next;	
}LNode, *LinkList;

第一题:

递归算法实现删除不带头结点的单链表L中所有值为x的结点

void delete_x(LinkList& L, int x){
	LNode* p;
	if(L == NULL) return;
	else if(L->data == x)
	{
		p = L;
		L = L->next;
		free(p);
		delete_x(L, x);
	}
	else
	{
		delete_x(L->next, x);
	}
}

第二题:

非递归实现删除所有x的结点 需要设置一个工作结点和前驱结点

void delete_x_1(LinkList& L,int x){
	LNode *p = L->next, *pre = L, *q;
	while(p != NULL){
		if(p->data == x){
			q = p;
			p = p->next;
			pre->next = p;
			free(q);
		}
		else{
			pre = p;
			p = p->next;
		}
	}
}

第三题:

逆序打印链表

void r_print(LinkList L){
	if(L->next != NULL) r_print(L->next);
	if(L != NULL) cout << L->data << ' ';
}

第四题:

删除单链表中的最小值结点
算法思路 : 依次扫描比较 然后记录最小值的结点和其前驱结点

void delete_min(LinkList& L){
	LNode* pre = L, *p = L->next;
	LNode *minpre = pre,*minp = p;
	while(p != NULL){
		if(p->data < minp->data){
			minpre = pre;
			minp = p;
		}
		pre = p;
		p = p->next;
	}
	// 删除最小值结点
	minpre->next = minp->next;
	free(minp);
}

第五题 :

使用空间复杂度O(1)的方法使得一个单链表逆置 可以使用头插法的方式实现逆置
方法一:

LinkList reverse_list(LinkList& L){
	LNode* p = L->next, *r;
	L->next = NULL;
	
	while(p != NULL){
		r = p->next;
		p->next = L->next;
		L->next = p;
		p = r;
	}
	return L;
}

方法二:

 //也可以用三个工作指针 实现指针指向的改变
LinkList reverse_list(LinkList& L){
	LNode *pre, *p = L->next, *r = p->next;
	p->next = NULL;   // 第一个结点特殊处理掉
	while(r != NULL){  // 当r指向空的时候 此时p就指向最后一个元素
		pre = p;
		p = r;
		r = r->next;
		p->next = pre;
	}
	L->next = p;
	return L;
}

第六题:

排序单链表
方法一:

// 时间负责度O(nlogn) 空间复杂度O(n) 以空间换时间的方式做
void Sort(LinkList& L){
	LNode* p = L->next;
	int tmp[100],cnt = 0;
	while(p != NULL){  // 在链表中取出所有数据
		tmp[cnt++] = p->data;
		p= p->next;
	}
	sort(tmp,tmp+cnt);
	
	p = L->next;  // 恢复工作指针
	int c = 0;
	while(p != NULL){  // 将排好序的数据存回链表中 
		p->data = tmp[c++];
		p = p->next;
	}
}

方法二:

// 利用直接插入排序的原理实现链表的排序
void Sort(LinkList& L){
	LNode *p = L->next, *pre;   // p是工作指针 , pre指针是用来从头开始遍历链表的 因为他不像数组一样可以随机访问
	LNode *r = p->next;  // p的后继指针,保证不断链
	p->next = NULL;
	p = r;
	while(p != NULL){
		r = p->next;
		pre = L;
		while(pre->next != NULL && pre->next->data < p->data) pre = pre->next;
		p->next = pre->next;
		pre->next = p;
		p = r;
	}
}

第七题 :

删除单链表中元素值介于(a,b)之间的值

void delete_seg(LinkList &L, int a,int b){
	LNode *p = L->next, *pre = L;
	while(p != NULL){
		if(p->data < b && p->data > a){
			pre->next = p->next;
			free(p);
			p = pre->next;
		}else {
			pre = p;
			p = p->next;
		}
	}
}

第八题:

求两个单链表中的公共结点
分析:如果两个链表存在一个公共结点,由于存在唯一next的域,因此从第一个公共结点开始,之后之后所有的结点都是重合的。
从拓扑结构上来看 是Y而不是X

LinkList search_commonNode(LinkList &L1,LinkList &L2){
	// 思路:如果链表等长直接依次比较,如果下一个结点指向相同 则含有相同结点。
	// 若不等长,去掉长链表前部分,变的等长,然后按照等长的思路去做
	int dist;
	int len1 = 0,len2 = 0;
	// 分别计算两链表的长度
	LNode *p = L1->next;
	while(p != NULL){
		len1 ++;
		p = p->next;
	}
	p = L2->next;
	while(p != NULL){
		len2 ++;
		p = p->next;
	}
	
	// 找出长链表和短链表
	LinkList longList,shortList;
	if(len1 > len2){
		longList = L1;
		shortList = L2;
		dist = len1 - len2;
	}else {
		longList = L2;
		shortList = L1;
		dist = len2 - len1;
	}
	
	while(dist--) longList = longList->next;
	
	while(longList != NULL){
		if(longList == shortList) return longList;
		else {
			longList = longList->next;
			shortList = shortList->next;
		}
	}
	// 无公共结点返回NULL
	return NULL;
}

第九题:

递增输出单链表中的结点,并销毁该链表(不允许使用数组作为辅助空间)

void min_delete(LinkList &L){
	while(L->next != NULL){
		LNode *pre = L,*p = L->next,*u;
		
		while(p->next != NULL){
			if(p->next->data < pre->next->data){
				pre = p; // 记录最小结点的前驱结点
			}
			p = p->next;
		}
		cout << pre->next->data << ' ';
		u = pre->next;
		pre->next = u->next;
		free(u);
	}
	free(L);
}

第十题:

将单链表A分成两个带头结点的单链表A和B,A含原表的奇数序号的元素,B含偶数序号的元素。

LinkList resolveList(LinkList& A){
	int cnt = 0; // 记录当前是奇数序号还是偶数序号
	LinkList B = new LNode;  // 创建B链表的头结点
	B->next = NULL;
	// 创建A B链表的尾指针 以及工作指针
	LNode *ra = A, *rb = B, *p;
	p = A->next;
	A->next = NULL;
	
	while(p != NULL){
		i ++;  // 这种方法可以规避掉偶数和奇数不等的情况
		if(i % 2 == 1){
			ra->next = p;
			ra = p;
		}else{
			rb->next = p;
			rb = p;
		}
		p = p->next;
	}
	ra->next = NULL;
	rb->next = NULL;
	return B;
}

第十一题:

和前面类似只不过需要将B链表进行逆序,并且本题中奇数和偶数个数相同。

LinkList resolveList(LinkList& A){
	// 只要B是头插法即可
	LinkList B = new LNode;
	B->next = NULL;
	LNode *p = A->next, *ra = A;
	A->next = NULL;
	
	while(p != NULL){
		ra->next = p;
		ra = p;
		p = p->next;
		
		if(p != NULL){   // 其实题目保证了是偶数 所以不用判断也行,加了也无所谓
			auto u = p->next;
			p->next = B->next;
			B->next = p;
			p = u;
		}
	}
	ra->next = NULL;
	return B;
}

第十二题:

去除链表当中的重复元素

void del_deplicates(LinkList &L){
	LNode *p = L->next, *q; // q为指向p后继指针
	// 依次比较两个指针指向的元素的值 相同则删除后继结点q,不同则指针后移
	
	while(p->next != NULL){
		q = p->next;
		if(q->data == p->data){
			p->next = q->next;
			free(q);
		}else{
			p = p->next;
		}
	}
}

第十三题:

将两个按递增顺序的单链表合并的成一个递减顺序的单链表

void merge_list(LinkList &La,LinkList &Lb){
	LNode *r,*pa = La->next, *pb = Lb->next;
	La->next=NULL;  // 合并的结果存放在La中
	
	while(pa && pb){ 
		if(pa->data < pb->data){
			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;
		}
	}
	
	// La 或者 Lb一方链表可能会剩余 特殊处理
	if(pb) pa = pb;
	
	while(pa){
		r = pa->next;
		pa->next = La->next;
		La->next = pa;
		
		pa = r;
	}
	
}

第十四题:

从A和B两个递增的单链表的公共元素生成C,不能破坏原结点

LinkList findSameOutList(LinkList &La,LinkList &Lb){
	LinkList Lc = new LNode;
	// pa pb 为工作结点, r为Lc的尾结点方便尾插,u为记录pa pb的后继结点,防止断链
	LNode *pa = La->next, *pb = Lb->next, *r, *u; 
	r = Lc;
	
	while(pa && pb){
		if(pa->data < pb->data) pa = pa->next;
		else if(pa->data > pb->data) pb = pb->data;
		else{
			u = new LNode;  // 不能破坏原结点 因此需要开辟新结点 并存入相关的值
			u->data = pa->data;
			r->next = u;
			r = u;
			pa = pa->next;
			pb = pb->next;
		}
	}
	r->next = NULL;
	return Lc;
}

第十五题:

求两个链表元素中的交集(考频高)返回一个链表,最后并销毁所有结点

LinkList intersection(LinkList &La,LinkList &Lb){
	LinkList Lc = new LNode;
	LNode *pa = La->next, *pb = Lb->next, *r , *u;
	
	r = Lc;
	while(pa && pb){
		if(pa->data == pb->data){
			u = new LNode;
			u->data = pa->data;
			r->next = u;
			r = u;
			
			// 释放pa pb指向的结点
			u = pa;
			pa = pa->next;
			free(u);
			u = pb;
			pb = pb->next;
			free(u);
		}else if(pa->data < pb->data){
			u = pa;
			pa = pa->next;
			free(u);
		}else {
			u = pb;
			pb = pb->next;
			free(u);
		}
	}
	
	if(pb) pa = pb;
	while(pa){
		u = pa;
		pa = pa->next;
		free(u);
	}
	r->next = NULL;  
	free(La);
	free(Lb);  //释放一下表头结点
	return Lc;
}

第十六题:

判断是一个单链表是否是另一个单链表的子序列

bool judge_seg(LinkList &La, LinkList &Lb){
	// 判断Lb是不是La的子序列 
	// 该算法时间复杂度为O(nm) 双指针算法
	LNode *pa = La->next, *pb = Lb->next, *pre = pa;
	
	while(pa && pb){
		if(pa->data == pb->data){
			pa = pa->next;
			pb = pb->next;
		}else{
			pre = pre->next;
			pa = pre;
			pb = Lb->next;
		}
	}
	
	if(!pb) return 1;  // pb 为空表示匹配成功
 	else return 0;
}

第十七题:

判断一个带头结点的循环双链表是否对称

typedef struct DNode{
	int data;
	struct DNode *prior, *next;
}DNode,*DLinkList;
int symmetry(DLinkList &L){
	DNode *pre = L->prior, *ne = L->next;
	while(pre->prior != ne->next && pre->prior != ne){
		if(pre->data == ne->data){
			pre = pre->prior;
			ne = ne->next;
		}else return 0;
	}
	return 1;
}

第十八题:

将两个循环单链表合并

void mergeCircleList(LinkList &La, LinkList &Lb){
	// 找到La的尾结点 让其指向Lb头结点, 然后找到Lb的尾结点让其指向La的头结点即可
	LNode *ra = La->next, *rb = Lb->next;
	while(ra->next != La) ra = ra->next;
	while(rb->next != Lb) rb = rb->next;
	// 合并两个单链表
	ra->next = Lb->next;  // Lb的头结点不存储信息,应该跳过
	rb->next = La;  // 最终合并之后的结果存放在La中
}

第十九题:

依次删除循环单链表中的最小值,并释放该最小值的结点

void delete_min(LinkList &L){
	// 删除最小值,定义pre指针指向最小值的前一个结点
	LNode *p = L->next,*pre = L;
	LNode *minp,*minpre;
	while(L->next != L){
		p = L->next;
		pre = L;
		while(p != L){
			if(p->data < minp->data){
				minp = p;
				minpre = pre;
			}
			p = p->next;
			pre = pre->next;
		}
		cout << minp->data << ' ';
		minpre->next = minp->next;
		free(minp);
	}
	free(L);
}

第二十题:

编写一个Locate(L,x) 函数,在带头结点的双向链表中,令元素值为x的结点中freq域的值+1,然后按freq非增排序,最近访问的排在前面。

LNode* Locate(LinkList &L,int x){
	LNode *p = L->next, *pre;
	while(p && p->data != x) p = p->next;
	if(!p)  return NULL;  //链表中无值为x的结点
	else{
		p->freq++;
		// 然后往前找到一个位置 保证其不减即可
		pre = p;
		p = p->prior;
		while(p != L && p->data <= pre->data) p = p->prior;
		
		// 将该结点插入p所指向的结点后面
		
		// 删除pre结点
		pre->prior->next = pre->next;  
		pre->next->prior = pre->prior;
		// 在p结点后插入pre结点
		pre->next = pre->next;
		p->next->prior = pre;
		p->next = pre;
		pre->prior = p;
	}
}

第二十一题:

单链表判环
算法思路:分别设置slow和fast指针都指向head结点,然后slow每次走一步,fast每次走两步。如果存在环的话,必然使得slow和fast在环上相遇。记head到环结点的长度为a,环上的长度为r ,x为环的入口点到像雨点的距离。必然满足 2*(a + x) = a + n * r + x ->> a = n * r - x ->> 最后一个点从起始点另一点从相遇点同时出发且步长一致,最后他们的相遇点必然是入口点。

LinkList judge_circle(LinkList &L){
	LNode *slow = L, *fast = L;
	while(fast != NULL && fast->next != NULL){
		fast = fast->next->next;
		slow = slow->next;
		if(fast == slow) break;
	}
	
	if(!fast || !fast->next)  return NULL;
	else {
		slow = slow;
		fast = L;
		while(slow != fast){
			slow = slow->next;
			fast = fast->next;
		}
	}
	return slow;
} 

第二十二题:

在带头结点的单链表中找到倒数第k个结点,输出其值,并返回查找状态
算法思路:
1) 直接遍历单链表的长度n,然后在遍历单链表找到第n - L位置即可
2) 定义一个指针p和q,想让p走k步,然后让p和q同时走,当p走到末尾的时候,此时q走到的一定是倒数第k个结点

bool findList_k(LinkList &L,int k){
	int cnt = k;
	LNode *p = L->next, *q = L->next;
	while(k -- ) {
		p = p->next;
		if(p) return 0;      // 如果p在走k步之前就已经是空的话 一定不存在倒数第K个数
	}
	
	// 否则之后一定存在倒数第k个数
	while(p){
		p = p->next;
		q = q->next;
	} 
	cout << q->data << endl;
	return 1;
}

第二十三题:

找出两个带头结点的单链表的公共后缀的起始点
算法思路:
假设st1比st2长的(但是不知道谁长也无所谓的),定义两个指针p1和p2分别指向st1和st2,然后同时扫描单链表,当其中一个指针指向为空的时候,令其指向另一个st,然后等到最长的那个扫描结束,即可或者与最短那个st等长的起始位置。接下来,分别扫描链表,当他们都指向相同结点的时候,那么这个结点一定是公共后缀的起始点。

LinkList findSameSuffix(LinkList &st1,LinkList &st2){
	LNode *p1 = st1->next, *p2 = st2->next;
	// 首先需要确定st1 和 st2的长度
	int len1 = 1,len2 = 1;
	while(p1) p1 = p1->next,len1++;
	while(p2) p2 = p2->next,len2++;
	
	// 我们始终保证st1 > st2
	if(len2 > len1) {
		auto tmp = stl;
		st1 = st2;
		st2 = tmp;
	} 
	
	p1 = st1->next;
	p2 = st2->next;
	while(p1){
		p1 = p1->next;
		p2 = p2->next;
		if(!p2) p2 = st1->next; 
	}
	p1 = st2->next;
	while(p2){
		
		if(p2 == p1) return p1;
		p1 = p1->next;
		p2 = p2->next;
	}
	return NULL;
}

第二十四题:

对于一个单链表删除绝对值相同的值,只保留一个。
算法思路:用一个辅助数组去记录该值是否出现过即可。

const int N = 1e5+10;
void delete_SameValue(LinkList &L){
	int vis[N];
	memset(vis,0,sizeof vis);  // 将辅助数组全部清空成零
	LNode *p = L->next,*pre = L;  // 定义工作指针和前驱指针
	
	while(p){  // 遍历单链表
		if(vis[p->data]){
			pre->next = p->next;
			free(p);
			p = pre->next;
		}else{
			vis[p->data] ++;
			pre = p;
			p = p->next;
		}
	}
}

第二十五题:

将线性表(a1,a2,a3…,an-1,an)变成(a1,an,a2,an-1,a3,an-2,…),该线性表为带头结点的单链表。
算法思路:将后半段指针指向逆序,然后分别用指针指向开头和最后,交替合并成一个链表。
首先要实现后半段的逆序,需要找到中点,设立一个指针slow和fast,slow走一步,fast走两步,当fast走到链表结束的时候,此时slow也就走到链表中点了。最后就是交替插入结点。

void Sort(LinkList &L){
	LNode *head = L, *rear, *slow = L, *fast = L;
	while(fast->next != NULL){
		slow = slow->next;
		fast = fast->next;
		if(fast->next != NULL) fast = fast->next;  // fast走两步
	}
	fast = slow->next;
	slow->next = NULL;
	while(fast != NULL){
		rear = fast->next;
		fast->next = slow;
		slow = fast;
		fast = rear;
	}
	
	// 最后slow是指向尾结点的
	head = L->next;
	rear = slow;
	L->next = NULL;
	while(rear){
		auto p = rear->next;
		rear->next = head->next;
		head->next = rear;
		head = rear->next;
		rear = p;
	}
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值