【数据结构】—— 链表类问题

一、基本操作

1. 三种常见考点

单链表进行排序时,一定用冒泡排序只交换两个节点的data就行了,不用发生指针的移动。

2. 链表类的都需要定义结构体

2)在二叉树中 

这里结合二叉树学习的,再来复习一下结构体的规则:

typedef struct node{
	char data;
	struct node *lchild,*rchild;
}*BitTree;  //和【int *p】一样,*在指针变量p的前面 
			//同理,node和BitTree尽量不要重名,和【int】与【p】的关系是一样的

3. 创建链表

1)节点

在C++中创建一个节点用new 结构体名()或者new 结构体名。

struct node*head;
head=new node();  //或者head=new node;

在C语言中

struct node *head;
head=(struct node*)malloc(sizeof(node);

这里的“head=”是指向的意思,不要认为此时head就是头结点,它只是头结点的指针,但是它可以操纵头结点。对于其他指针也一样,比如now指针,它并不是当前节点,只是指向当前节点,但是它可以操纵当前节点。

 2)在二叉树中

        复习了链表以后,才理解了创建二叉树时的new node操作—— 创建一个新结点,让结构体指针指向这个新结点

3)尾插法创建链表

struct node *head,*tail,*now;

head=new node(); //创建头结点
head->next=null; // 避免野指针

tail=head; //此时头结点就是尾结点


for(int i=1;i<=一共有多少个;i++){
    now=new node();
    tail->next=now;
    tail=tail->next;
    tail->next=null;
}

4)  头插法

        我不喜欢用头插法 

4. 这样写指针的连接逻辑更清晰,不要都堆在一起

 5. 链表判断是否是最后一个节点。一律用当前节点是否为空(list1!=null),不要用当前节点的下一个结点是否为空(list1->next!=null)

6. 有时候为了给指针pre和now的赋初始值,需要在整个主逻辑开始前单独比较前一个链表的前两个结点或者两个链表的第一个结点。

例1)单独比较两个链表的第一个结点

 

 例2) 单独比较前一个链表的前两个结点

二、例题

2.1 约瑟夫环(猴子报数)

输入
10
2 5
输出
6,1,7,3,10,9,2,5,8,4

# include<bits/stdc++.h>
using namespace std;

struct node{
	int num;
	struct node *next;
};

int main(){
	int totalMonkey; //猴子总数 
	int start;  //从第几只猴子开始 
	int number;  //报的数
	struct node *head,*tail,*now,*pre;
	/*---------用户输入------------*/
	cin>>totalMonkey;
	cin>>start;
	cin>>number;
	/*----------创建约瑟夫环-------*/ 
	head=new node();  //创建头结点 
	head->num=1;
	head->next=NULL;
	tail=head;
	for(int i=2;i<=totalMonkey;i++){ //尾插法创建链表 
		now=new node();
		now->num=i; 
		tail->next=now;
		tail=now;
		tail->next=NULL; 
	}
	tail->next=head;//连成一个环
	 
//	/*------约瑟夫环输出测试-------*/
//	for(int i=1;i<=totalMonkey;i++){
//		cout<<head->num<<" ";
//		head=head->next;
//	} 

    /*-----------游戏过程---------*/
    //先找到最开始报数的猴子,用now标注
	now=head;
	pre=tail; //pre在now前面一个 
	for(int i=1;i<=start-1;i++){
		now=now->next;
		pre=pre->next;
	} 
	while(now->next!=now){
		//报数ing
		for(int i=1;i<=number-1;i++){
			now=now->next;
			pre=pre->next;
		} 
		//此时now指向的猴子结点退出
		cout<<now->num<<",";
		pre->next=now->next;
		now->next=NULL;
		now=pre->next; 
	}
	cout<<now->num; //最后一只猴子 
    return 0;
}

 发现自己深入理解一下结点指针和结点的关系后,写起代码来比以前简洁多了!

2.2 合并两个有序列表

 

 第一种思路是再创建一个新链表list3,把list1和list2的元素往list3上挪。

1)指针使用情况。只需要新建一个list3指针返回用,因为list3采用尾插法,所以只需要一个指针now3指向当前的尾结点就可以了。list1和list2分别指示第一个链表和第二个链表正在进行比较的结点。

2)判断是否是最后一个节点。一律用当前节点是否为空(list1!=null),不要用当前节点的下一个结点是否为空(list1->next!=null)!

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        /*------先处理特殊情况-----*/
        if(list1==nullptr&&list2==nullptr)
            return nullptr;
        else if(list1==nullptr&&list2!=nullptr)
            return list2;
        else if(list1!=nullptr&&list2==nullptr)
            return list1;
        /*---确定list3,now3的头结点--*/
        ListNode *list3,*now3;
        if(list1->val<=list2->val){
            list3=list1;
            now3=list1;
            list1=list1->next;
            list3->next=nullptr;
        }else{
            list3=list2;
            now3=list2;
            list2=list2->next;
            list3->next=nullptr;
        }
        /*---确定list3头结点之后的结点-*/
        while(list1!=nullptr&&list2!=nullptr){//一旦有一个链表走到了尽头就跳出循环
            if(list1->val<=list2->val){
                now3->next=list1;
                list1=list1->next;
                now3=now3->next;
                now3->next=nullptr;            
            }else{
                now3->next=list2;
                list2=list2->next;
                now3=now3->next;
                now3->next=nullptr;
            }
        }
        /*----处理后面剩的-----*/
        if(list1==nullptr)
            now3->next=list2;
        else
            now3->next=list1;
        
        return list3;
    }
};

另一个思路是把list1作为最终返回的链表,把list2的结点插入到list1上,和思路(一)的区别在于,思路(一)是采用尾插法,思路(二)是插入结点。但是不推荐用这种。要思考的细节太多了。

1)必须想到令头结点小的作为list1这一点!

2)指针使用情况。在list1中,除了指针now1用来指向当前正在被比较的结点,还需要有一个pre1指向前一个结点,这样才能进行插入操作。在list2中,不仅需要now2指向当前正在被比较的结点,还需要一个和now2指向同一位置的list2去辅助now2,要不然list2的结点插入完后就找不到下一个结点了。

3)判断是否是最后一个节点。同样一律用当前节点是否为空(list1!=null)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        /*------先处理特殊情况-----*/
        if(list1==nullptr&&list2==nullptr)
            return nullptr;
        else if(list1==nullptr&&list2!=nullptr)
            return list2;
        else if(list1!=nullptr&&list2==nullptr)
            return list1;
        /*---令头结点小的作为list1--*/
        if(list1->val>list2->val){
            ListNode *temp = list1;
            list1=list2;
            list2=temp;
        }
        /*-----list2挪到list1上----*/
        ListNode *now1,*now2,*pre1;
        now1=list1->next;  //头结点已经比较过了,所以从第二个开始
        pre1=list1;
        now2=list2;
        while(now1!=nullptr&&now2!=nullptr){
           if(now1->val > now2->val){
               //预处理
               list2=list2->next;
               //连接
               now2->next=now1;
               pre1->next=now2;
               //后处理
               pre1=pre1->next;
               now2=list2;
           }else{
               now1=now1->next;
               pre1=pre1->next;
           }
        }
        /*-----处理剩下的----*/
        if(now1==nullptr){
            pre1->next=list2;
        }
        return list1;
    }
};

2.3 链表排序

写的有些复杂了,但这就是我能想到的方法。大体思路是这样的:

第一遍冒泡排序单独拎出来进行,一个while循环就够了。

第二遍到第length-1遍冒泡排序需要两个while循环。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        /*-----处理特殊情况----*/
        if(head==nullptr||head->next==nullptr)
            return head;

        ListNode *pre,*now,*after,*tail;
        tail=nullptr;
        /*========================第一遍冒泡排序,单独拿出来是为了确定tail=================*/
        /*---确定前两个结点中小的为头结点,
        确定pre,now,after的位置----*/
        if(head->val > head->next->val){
            //确定head
            ListNode *temp=head;
            ListNode *headNext=head->next;
            head->next=head->next->next;
            headNext->next=head;
            head=headNext;
            //确定pre,now,after的位置
            pre=head;
            now=head->next;
            after=now->next;
        }else{
            pre=head;
            now=head->next;
            after=now->next;
        }     
        while(after!=nullptr){
            if(now->val>after->val){
                //连接
                pre->next=after;
                now->next=after->next;
                after->next=now;
                //后处理
                pre=pre->next;
                after=now->next;
            }else{
                pre=pre->next;
                now=now->next;
                after=after->next;
             }
        }
        tail=now;
        /*=============================第2到length-1遍冒泡==============================*/
        /*---确定前两个结点中小的为头结点,
        确定pre,now,after的位置----*/
        while(tail!=head->next){
             if(head->val > head->next->val){
                //确定head
                ListNode *temp=head;
                ListNode *headNext=head->next;
                head->next=head->next->next;
                headNext->next=head;
                head=headNext;
                //确定pre,now,after的位置
                pre=head;
                now=head->next;
                after=now->next;
            }else{
                pre=head;
                now=head->next;
                after=now->next;
            }

            while(after!=tail){
                if(now->val>after->val){
                    //连接
                    pre->next=after;
                    now->next=after->next;
                    after->next=now;
                    //后处理
                    pre=pre->next;
                    after=now->next;
                }else{
                    pre=pre->next;
                    now=now->next;
                    after=after->next;
                }
            }  
            tail=now;
        }
          return head;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值