C++\C 链表相关问题的思考和总结

如何完成链表的逆序

问题一:已知链表头节点指针head,将链表逆序。(不可申请额外空间)

逆序就好比人们排队时的变换阵型的过程,比如,原本已经排好的队伍,我想要把排头的放在最后,排尾的放到前面来,完成队伍的翻转,这个过程就是把每次排头的人放在新队伍所有人的前面,从而做到原队伍的翻转。

上述的过程主要运用的是倒插法来解决问题,思路如下:

新建一个同类型的空指针new_head,在循环的操作下执行以下操作:一个原链表的头指针的下一节点next,将头指针head连接到空指针new_head的前面,new_head向前挪一格,即指向head所指向的地址,head指向next,也就是剩余原链表的头节点;直至头节点为空而停止操作。

代码如下:

#include<iostream>
#define _CRT_SECURE_NO_WARNINGS

using namespace std;

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

ListNode* reverseList(ListNode* head) {
	ListNode *new_head = NULL;
	while(head){
		ListNode *next = head->next;
		head->next = new_head;
		new_head = head;
		head = next;
	}
	return new_head;
}

int main(){	
	ListNode a(1);
	ListNode b(2);
	ListNode c(3);
	ListNode d(4);
	ListNode e(5);
	a.next = &b;
	b.next = &c;
	c.next = &d;
	d.next = &e;
	ListNode *head = &a;
	printf("Before reverse:\n");
	while(head){
		printf("%d\n", head->val);
		head = head->next;
	}
	head = reverseList(&a);
	printf("After reverse:\n");
	while(head){
		printf("%d\n", head->val);
		head = head->next;
	}
	return 0;
}

问题二:选择性的逆序m到n的链表[1<=m<=n<=链表长度](进阶)

在上一个问题的基础上施加更多的要求,考验代码灵活应变的能力。

对于原来的链表,我要找到第m个节点和第n个节点,将他们逆序之后再连接回链表中,其中我们就要思考到关键性的问题——①当我们完成了m到n的节点逆序之后,我们该如何找到第m-1个节点和第n+1个节点,使得逆序的链表链接回原链表?②遍历到第m个节点之后,继续遍历,如何防止第m个节点位置的丢失

这时,就涉及到对于关键位置的节点地址保存的问题,例如,建立pre_head指针在遍历到第m个节点时,指向第m-1个节点,同时建立指针m_Node指向第m个指针,在遍历到第n个节点时,建立指针new_head指向第n个节点,next指针将做为下一连接的节点。

代码如下:

#include <stdio.h>

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        int change_len = n - m + 1;
        ListNode *pre_head = NULL;
        ListNode *result = head;
        while(head && --m){
        	pre_head = head;
        	head = head->next;
        }
        ListNode *modify_list_tail = head;
        
        ListNode *new_head = NULL;
		while(head && change_len){
			ListNode *next = head->next;
			head->next = new_head;
			new_head = head;
			head = next;
			change_len--;
		}
		modify_list_tail->next = head;
		
		if (pre_head){
			pre_head->next = new_head;
		}
		else{
			result = new_head;
		}
		return result;
    }
};

int main(){	
	ListNode a(1);
	ListNode b(2);
	ListNode c(3);
	ListNode d(4);
	ListNode e(5);
	a.next = &b;
	b.next = &c;
	c.next = &d;
	d.next = &e;
	Solution solve;
	ListNode *head = solve.reverseBetween(&a, 2, 4);	
	while(head){
		printf("%d ", head->val);
		head = head->next;
	}
	return 0;
}

结果为:
1 4 3 5 2

链表的交点问题

问题一:两个链表之间的交点

思路一:我将其中一个链表的指针都放入到一个vector的容器中,循环headB指针,通过find函数,说明headB的指针是否存在于这个容器中,当存在,返回指针,不存在,返回NULL。(有存储空间上的使用)

思路二:先计算出两个链表的长度,把较长的链表指针头走到与另一链表相同长度的位置,再同时向后遍历,当两个指针同时指向同一地址时,说明有交点,返回指针,不存在交点,返回NULL。(无存储空间上的使用)
如图所示

所以对比上述两种方法,显然思路二更加优良,这里提供思路二的解题方法。

代码如下:

#include <stdio.h>

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

int get_list_length(ListNode *head){
	int len = 0;
	while(head){
		len++;
		head = head->next;
	}
	return len;
}

ListNode *forward_long_list(int long_len, 
				int short_len, ListNode *head){
	int delta = long_len - short_len;
	while(head && delta){
		head = head->next;
		delta--;
	}
	return head;
}

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
   		int list_A_len = get_list_length(headA);
   		int list_B_len = get_list_length(headB);   		
   		if (list_A_len > list_B_len){
   			headA = forward_long_list(list_A_len, list_B_len, headA);
	   	}
	   	else{
	   		headB = forward_long_list(list_B_len, list_A_len, headB);
	   	}   		
        while(headA && headB){
        	if (headA == headB){
	        	return headA;
	        }
	        headA = headA->next;
	        headB = headB->next;
        }
        return NULL;
    }
};

int main(){
	ListNode a1(1);
	ListNode a2(2);
	ListNode b1(3);
	ListNode b2(4);
	ListNode b3(5);
	ListNode c1(6);
	ListNode c2(7);
	ListNode c3(8);
	a1.next = &a2;
	a2.next = &c1;
	c1.next = &c2;
	c2.next = &c3;
	b1.next = &b2;
	b2.next = &b3;
	b3.next = &c1;	
	Solution solve;
	ListNode *result = solve.getIntersectionNode(&a1, &b1);
	printf("%d\n", result->val);
	return 0;
}

问题二:单个链表的交点(成环)

首先需要了解这样的一个有环链表具有什么样的特点:①最后没有NULL 进行结尾;②存在成环时的首节点(即环交点)。

思路一:在寻找环交点的过程也可以仿照上一问题的思路一,将链表从头节点进行遍历,建立一个存放链表指针的vector容器,每遍历一个指针就判断指针是否存在于容器中,在存入容器中,当链表指针成功将链表中的环遍历一遍,进行下一次时,首指针(即成环首交点处的地址)已存在容器中,则返回该指针,否则不存在环,返回NULL。(有存储空间的使用)

思路二:一个经典的方法:快慢指针法;我们建立两种指针,一次走两步的fast指针和一次走一步的slow指针,当fast指针与slow指针相遇时,从头指针head和相遇时的节点指针meet到环交点指针的距离相等。(无存储空间的使用)

a:头节点到环交点的距离
b:环交点到相遇节点的距离
c:相遇节点到换节点的距离

(字有点凌乱,见笑了)

代码如下:

#include <stdio.h>

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
    	ListNode *fast = head;
    	ListNode *slow = head;
    	ListNode *meet = NULL;
    	while(fast){
    		slow = slow->next;
    		fast = fast->next;
    		if (!fast){
		    	return NULL;
		    }
		    fast = fast->next;
		    if (fast == slow){
    			meet = fast;
    			break;
    		}
	    }
	    if (meet == NULL){
    		return NULL;
    	}
    	while(head && meet){
	    	if (head == meet){
	    		return head;
	    	}
	    	head = head->next;
	    	meet = meet->next;
	    }
        return NULL;
    }
};

int main(){
	ListNode a(1);
	ListNode b(2);
	ListNode c(3);
	ListNode d(4);
	ListNode e(5);
	ListNode f(6);
	ListNode g(7);
	a.next = &b;
	b.next = &c;
	c.next = &d;
	d.next = &e;
	e.next = &f;
	f.next = &g;
	g.next = &c;
	Solution solve;
	ListNode *node = solve.detectCycle(&a);
	if (node){
		printf("%d\n", node->val);
	}
	else{
		printf("NULL\n");
	}
	return 0;
}

链表的深拷贝

题目讲解:一个链表中,有两个链接链表的节点,一个next,按链表顺序链接链表,另一个random,随机链接链表的节点(包括自身)求这个链表的深拷贝
简单形式,如图所示:
深拷贝的连接形式
这道题,我打算改变原有的罗列顺序,先将代码放出,后将解题的思路梳理出来,希望道友们能懂得看似复杂的代码背后,是一个简单、浅显的道理!
代码如下:

#include <stdio.h>
		
struct RandomListNode {
	int label;
	RandomListNode *next, *random;
	RandomListNode(int x) : label(x), next(NULL), random(NULL) {}
};

#include <map>
#include <vector>

class Solution {
public:
    RandomListNode *copyRandomList(RandomListNode *head) {
    	std::map<RandomListNode *, int> node_map;
    	std::vector<RandomListNode *> node_vec;
    	RandomListNode *ptr = head;
    	int i = 0;
    	while (ptr){
	    	node_vec.push_back(new RandomListNode(ptr->label));
	    	node_map[ptr] = i;
	    	ptr = ptr->next;
	    	i++;
	    }
	    node_vec.push_back(0);
	    ptr = head;
	    i = 0;
	    while(ptr){
    		node_vec[i]->next = node_vec[i+1];
    		if (ptr->random){
    			int id = node_map[ptr->random];
		    	node_vec[i]->random = node_vec[id];
		    }
    		ptr = ptr->next;
    		i++;
    	}
    	return node_vec[0];
    }
};

int main(){
	RandomListNode a(1);
	RandomListNode b(2);
	RandomListNode c(3);
	RandomListNode d(4);
	RandomListNode e(5);
	a.next = &b;
	b.next = &c;
	c.next = &d;
	d.next = &e;	
	a.random = &c;
	b.random = &d;
	c.random = &c;
	e.random = &d;	
	Solution solve;
	RandomListNode *head = solve.copyRandomList(&a);	
	while(head){
		printf("label = %d ", head->label);
		if (head->random){
			printf("rand = %d\n", head->random->label);
		}
		else{
			printf("rand = NULL\n");
		}
		head = head->next;
	}
	return 0;

看完上述的过程,道友们是否懂得了这个代码所要传达的主体思路了呢?

那么接下来,由我来梳理一下这个主题过程:
对于这样凌乱看似毫无章法的链表,首先要做的就是将其所包含的信息给梳理出来,比如这里就通过一个node_map容器储存链表指针和节点号的方式,梳理出了由next节点排序出的链表格式,第二个链表则为遍历地存储新链表节点,之后通过梳理出的node_map关系,重新把node_vec容器中的节点连接起来,形成新的链表进行输出,从而完成链表的深拷贝。
如图所示:(节点地址与节点序号对应)
思路图

致谢

本章知识点和思路由小象学院相关视频提供,由本人学习并梳理得出,希望自己加深记忆的同时,也能给大家提供更多有关于链表的知识点,谢谢支持!

三种不同的方法,挺不错的! #include<stdio.h> #include<stdlib.h> #include<string.h> #define N 100 typedef struct SList { char data[N]; //字符数组 struct SList *next; //定义链表头指针 }SList,*ListPointer; /*typedef struct List { SList *head; }List,* ListPointer; */ void initList(ListPointer &lp) { lp=(SList *)malloc(sizeof(SList));//初始化链表 lp->next=lp; //链表头指针指向本身,实现链表循环 } void output(ListPointer lp) // 自定义输出函数 { SList *ep; ep=lp; while(ep->next!=lp) //判定条件 当指针重新指向头指针输出结束 { ep=ep->next; printf("%s ",ep->data); } } void revert(ListPointer lp) // 链表的逆置 { SList *p,*q; p=lp;q=NULL;lp=NULL; while(p) { q=p->next; p->next=lp; lp=p; p=q; } /*方法二 SList *p,*q,*end; p=lp->next; q=p->next; end=p; while(q!=lp) { lp->next=q; q=q->next; lp->next->next=p; p=lp->next; } end->next=lp; */ } void add_char(char *p,ListPointer lp) //将输入的字符传递给链表 { SList * ep; ep=lp; while(ep->next!=lp) //判定条件 当指针重新指向头指针输出结束 { ep=ep->next; } ep->next=(SList *)malloc(sizeof(SList)); //开辟空间存储 strcpy(ep->next->data,p); //字符的传递 ep->next->next=lp; } void main() { ListPointer L; char str[N]; initList(L); printf("输入#以结束\n");//确定输入终止条件 while(1) { scanf("%s",str); if(*str=='#') //判定条件 { break; } add_char(str,L); } printf("初始序列为:"); output(L); printf("\n"); revert(L); printf("逆置后为:"); output(L); printf("\n"); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值