链表---C实现

目录

1.链表的概念

2.链表的分类

1. 单向或者双向

2. 带头或者不带头

3. 循环或者非循环

3.链表的实现

3.1.无头单向非循环链表

3.1.1. 实现框架​​​​​​​

3.1.2.1. push_back()的实现问题和解决办法:

3.1.2.2. push_front()的实现 

 3.1.2.3. pop_back()的实现

3.1.2.4.pop_front()的实现 

3.1.2.5. find()的实现

3.1.2.6. insert()的实现

3.1.2.7. erase()的实现 

3.1.2.8. print()的实现

3.1.2.9. build()的实现

3.1.2.10. destroy()的实现 

3.2.带头双向循环链表的实现

3.2.1. 实现框架

3.2.2. 整体实现

 4.单链表的练习题

4.1. 第一题:移除链表元素

4.2. 第二题:反转链表

4.3. 第三题:不提供头结点,删除pos

4.4. 第四题:链表的中间节点

4.5. 第五题:合并两个有序链表

4.6. 第六题:链表中倒数第k个节点

4.7. 第七题:链表分割

4.8. 第八题:链表回文结构

 4.9. 第九题:相交链表

4.10. 第十题:环形链表

4.11. 第十一题:环形链表Ⅱ:

4.11.1. 第一种方法:

4.11.2. 第二种方法:

4.12. 第十二题:复制带随即指针的链表

4.12.1. 代码实现: 

5. 链表和顺序表的区别:


1.链表的概念

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

2.链表的分类

实际中链表的结构非常多样,以下情况组合起来就有 8 种链表结构:

1. 单向或者双向

         

2. 带头或者不带头

3. 循环或者非循环

 

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构: 

注意:上面的图都是逻辑图,实际是不会有这些箭头的;

在这里以无头单向非循环链表举例,我们画一下它的物理图 

为了更好理解链表的实际存储,我们用编译器进行调试,查看每个结点的地址,看看是否符合上述物理图。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int node_val_type;

typedef struct single_link_list_node
{
	struct single_link_list_node* next;
	node_val_type val;
}SLNode;

void print_node_val(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}
int main()
{
	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
	assert(node1);
	node1->val = 1;

	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
	assert(node2);
	node2->val = 2;

	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
	assert(node3);
	node3->val = 3;

	node1->next = node2;
	node2->next = node3;
	node3->next = NULL;

	print_node_val(node1);

	free(node1);
	free(node2);
	free(node3);
}

 

显然,经过对比是符合我们预期,即链表的实际存储是按照物理图的方式进行存储的;

 1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表: 结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

3.链表的实现

3.1.无头单向非循环链表

3.1.1. 实现框架

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int node_val_type;

typedef struct single_link_list_node
{
	struct single_link_list_node* next;
	node_val_type val;
}SLNode;


void print_node_val(SLNode* phead);
void push_back_node_val(SLNode** pphead,node_val_type val);
void push_front_node_val(SLNode** pphead,node_val_type val);
void pop_back_node_val(SLNode** pphead);
void pop_front_node_val(SLNode** pphead);
SLNode* find_special_val_node(SLNode* phead,node_val_type val);
void insert_node_val(SLNode** pphead,SLNode* pos,node_val_type val); //在pos位置前面插入
void insert_node_val_after(SLNode* pos, node_val_type val); //在pos位置后面插入
void erase_node_val(SLNode** pphead,SLNode* pos);

3.1.2.1. push_back()的实现问题和解决办法:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int node_val_type;

typedef struct single_link_list_node
{
	struct single_link_list_node* next;
	node_val_type val;
}SLNode;

void push_back(SLNode* phead, node_val_type val)
{
	//1.创建一个new node
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	assert(newnode);
	newnode->val = val;
	newnode->next = NULL;
	//2.分两种情况处理

	//a.空表
	if (phead == NULL)
	{
		phead = newnode;
	}
	//b.不是空表
	else
	{
		//1.找尾
		SLNode* tail = phead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

void print_node_val(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}
int main()
{
	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
	assert(node1);
	node1->val = 1;

	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
	assert(node2);
	node2->val = 2;

	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
	assert(node3);
	node3->val = 3;

	node1->next = node2;
	node2->next = node3;
	node3->next = NULL;

	print_node_val(node1);

	push_back(node1, 4);
	push_back(node1, 5);
	push_back(node1, 6);
	print_node_val(node1);
}

结果:

1->2->3->NULL
1->2->3->4->5->6->NULL 

结果符合预期;但是有些情况我们是不是要在空表的基础上push_back(),这种场景是存在的,那么我们的push_back()能满足这种情况吗?

int main()
{
	SLNode* phead = NULL;
	push_back(phead, 1);
	push_back(phead, 2);
	push_back(phead, 3);
	print_node_val(phead);
}

结果:

NULL

结果与预期不符合,奇怪了,怎么回事呢?为什么当是一个空表的时候,数据没有插入进去呢?遇到问题,我们去调试,

 当我在函数内部调试时,发现数据的确被插入进去了呀,OK,不急我们继续往下看,

我们惊奇的发现,出了push_back()这个函数作用域,我们的phead竟然又变成NULL了,里面的数据不见了也情有可原;那为什么呢?OK要理解这个问题的原因,我们先看下面的代码:

void Func(double x)
{
	x = 10;
}

int main()
{
	double d = 5;
	printf("old d = %lf\n", d);
	Func(d);
	printf("new d = %lf\n", d);
}

 结果:

old d = 5
new d = 5

 这个我们应该能理解,在Func1里面被改变的x是d的拷贝,在这个函数内部的改变无法影响到实参,如果我们要改变,需要传递d的地址,即我们要改变double的值,需要传递double*,即double类型的地址,那么我们要改变SLNode*,那么我们要传递SLNode**,即这个节点的地址;因此上面的push_back()之所以数据没有正常插入,是因为它是值传递,因此我们解决的方案是址传递;

//要改变 phead,需要传递 &phead,即形参需要用二级指针接受
void push_back_node_val(SLNode** pphead,node_val_type val)    
{    
  assert(pphead);      #pphead是(*pphead)的地址,正常情况下pphead不可能为NULL
  //1.创建一个新节点    
  SLNode* newnode = build_new_node(val);    
  //2.插入数据,两种情况    
  //a.空表    
  if(*pphead == NULL)    
  {    
    *pphead = newnode;    
  }    
  //b.不是空表    
  else    
  {    
    SLNode* tail = *pphead;    
    while(tail->next != NULL)    
    {    
      tail = tail->next;    
    }    
    tail->next = newnode;    
  }    
}

void Test(void)    
{    
  SLNode* phead = NULL;    
  push_back_node_val(&phead,1);      
}

3.1.2.2. push_front()的实现 

void push_front_node_val(SLNode** pphead,node_val_type val)
{
  assert(pphead);
  //1.创建一个new node
  SLNode* newnode = build_new_node(val);
  //2.插入数据
  newnode->next = *pphead;
  *pphead = newnode;
}

 3.1.2.3. pop_back()的实现

void pop_back_node_val(SLNode** pphead)
{
  assert(pphead);
  //1.空表不能删
  assert(*pphead);
  //2.删除非空表,两种情况
  //a.只有一个数据
  if((*pphead)->next == NULL)
  {
    free(*pphead);
    *pphead = NULL;
  }
  //b.2个或2个以上的数据
  else                                                                
  {                        
    //遍历得到tail的前一个node                                           
    SLNode* tail_prev = *pphead;                                      
    while(tail_prev->next->next != NULL)                              
    {                                                                 
      tail_prev = tail_prev->next;                                    
    }                                                                 
    SLNode* tail = tail_prev->next;                                   
    free(tail);                                                       
    tail = NULL;                                                      
    //3.最关键的一步,需要把删除后的最后一个数据的next域置为NULL(防止野指针)     
    tail_prev->next = NULL;                                                                               
  }                                                                   
}

3.1.2.4.pop_front()的实现 

void pop_front_node_val(SLNode** pphead)                              
{                                                                     
  assert(pphead);
  //1.空表不能删
  assert(*pphead);
  //2.删除数据
  SLNode* head_next = (*pphead)->next;
  free(*pphead);
  *pphead = head_next;
}

3.1.2.5. find()的实现

SLNode* find_special_val_node(SLNode* phead,node_val_type val)
{
  SLNode* cur = phead;
  while(cur != NULL)
  {
    if(cur->val == val)
      return cur;         # 返回目标节点

    cur = cur->next;
  }
  return NULL;  # 代表没找到
}

3.1.2.6. insert()的实现

//在pos位置之前插入
void insert_node_val(SLNode** pphead,SLNode* pos,node_val_type val)
{
    assert(pphead);
    //1.pos位置需要有效
    assert(pos);
    //2.创建一个new node
    SLNode* newnode = build_new_node(val);
    //插入数据,两种情况
    //a.头插
    if(pos == *pphead)
    {
      newnode->next = *pphead; 
      *pphead = newnode;
    }
    //非头插
    else
    {
      SLNode* pos_prev = *pphead;
      while(pos_prev->next != pos)
      {
        pos_prev = pos_prev->next;
      }
      pos_prev->next = newnode;
      newnode->next = pos;                                                                                
    }
}

//在pos位置之后插入
void insert_node_val_after(SLNode* pos, node_val_type val)                        
{                                                                                 
  assert(pos);  
  SLNode* newnode = build_new_node(val);  
  newnode->next = pos->next;  
  pos->next = newnode;                                                                                    
}

3.1.2.7. erase()的实现 

//删除pos位置
void erase_node_val(SLNode** pphead,SLNode* pos)
{
  assert(pphead);
  assert(pos);
  //1.头删
  if(pos == *pphead)
  {
    //a.只有一个节点
    if((*pphead)->next == NULL)
    {
      free(*pphead);
      *pphead = NULL;
    }
    //b.多个节点                                                                                          
    else
    {
      SLNode* head_next = (*pphead)->next;
      free(*pphead);
      *pphead = head_next;
    }
  }
  else
  {
    SLNode* pos_prev = *pphead;
    while(pos_prev->next != pos)
    {
      pos_prev = pos_prev->next;
    }
    SLNode* pos_next = pos->next;
    free(pos);
    pos_prev->next = pos_next;
  }
}
//删除pos的下一个位置
void erase_node_val_after(SLNode* pos)                                            
{                                                                                 
  assert(pos && pos->next != NULL);  #pos不为空 && pos不能为尾节点 
  SLNode* del = pos->next;  
  SLNode* del_next = del->next;  
  free(del);
  del = NULL;                                                                                             
  pos->next = del_next;                       
} 

3.1.2.8. print()的实现

void print_node_val(SLNode* phead)
{
  //assert(phead);    #这里是不能断言的,空表是可以打印的   
  SLNode* cur = phead;
  while(cur != NULL)
  {
    printf("%d->",cur->val);
    cur = cur->next;
  }
  printf("NULL\n");
}

3.1.2.9. build()的实现

SLNode* build_new_node(node_val_type val)  //创建一个值为val的新节点      
{                                     
  SLNode* ret = (SLNode*)malloc(sizeof(SLNode));      
  assert(ret);                     
  ret->val = val;             
  ret->next = NULL;      
  return ret;      
}

3.1.2.10. destroy()的实现 

void destroy(list_node** pphead)                                                                                          
{                                                                                          
  list_node* cur = *pphead;                                                                                          
  list_node* next = NULL;                                                                                          
  while(cur != NULL)                                                                                          
  {                                                                                          
    next = cur->_next;                                                                                          
    free(cur);                                                                                          
    cur = next;                                                                                          
  }                                                                                          
}

3.2.带头双向循环链表的实现

3.2.1. 实现框架

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int list_val_type;
typedef struct double_link_list
{
  struct double_link_list* _prev;
  struct double_link_list* _next;
  list_val_type _val;
}DLlist,*PDlist;

//构造哨兵位的头结点
DLlist* build_head();
void print(DLlist* phead);
void push_back(DLlist* phead,list_val_type val);
void push_front(PDlist phead,list_val_type val);
void pop_back(DLlist* phead);
void pop_front(DLlist* phead);
void destroy(PDlist phead);
bool empty(PDlist phead);
PDlist find(PDlist phead,list_val_type val);
//在pos位置前插入新节点
void insert(PDlist pos,list_val_type val);
//删除pos所在的节点
void erase(PDlist pos);

3.2.2. 整体实现

#include "double_link_list.h"
# 构造哨兵位的头节点
DLlist* build_head()
{
  DLlist* newnode = (DLlist*)malloc(sizeof(DLlist));
  assert(newnode);
  newnode->_prev = newnode->_next = newnode;
  return newnode;
}

void print(DLlist* phead)
{
  assert(phead);
  DLlist* cur = phead->_next;
  while(cur != phead->_prev)
  {
    printf("%d->",cur->_val);
    cur = cur->_next;
  }
  if(cur != phead)
    printf("%d\n",cur->_val);
}
# 构造一个有效节点
PDlist build_node(list_val_type val)
{
  PDlist newnode = (PDlist)malloc(sizeof(DLlist));
  assert(newnode);
  newnode->_val = val;
  newnode->_prev = newnode->_next = newnode;
  return newnode;
}

bool empty(PDlist phead)
{
  return phead->_next == phead;
}

PDlist find(DLlist* phead,list_val_type val)
{
  //1. 从第一个有效节点开始
  DLlist* cur = phead->_next;
  while(cur != phead)
  {
    if(cur->_val == val)
      return cur;
    cur = cur->_next;
  }
  return NULL;
}

void push_back(PDlist phead, list_val_type val)
{
  //1.哨兵位的头节点不可能为空
  assert(phead);
  //2.造新节点
  PDlist newnode = build_node(val);
  //3. 找尾
  DLlist* tail = phead->_prev;
  tail->_next = newnode;
  newnode->_prev = tail;
  newnode->_next = phead;
  phead->_prev = newnode;
}

void push_front(PDlist phead,list_val_type val)
{
  //1.哨兵位的头结点不可能为空
  assert(phead);
  //2.造新节点
  PDlist newnode = build_node(val);
  assert(newnode);
  //3.找到哨兵位的_next,在phead和phead->_next之间插入新节点
  DLlist* head_next = phead->_next;
  newnode->_next = head_next;
  head_next->_prev = newnode;
  newnode->_prev = phead;
  phead->_next = newnode;
}

void pop_back(PDlist phead)
{
  //1.哨兵位头节点不为空
  assert(phead);
  //2.空表不可删(不包含哨兵位的头节点)
  assert(!empty(phead));
  //3.找尾节点tail的_prev,将tail->_prev和phead连接起来,释放tail
  DLlist* tail = phead->_prev;
  DLlist* tail_prev = tail->_prev;
  tail_prev->_next = phead;
  phead->_prev = tail_prev;
  free(tail);
}

void pop_front(DLlist* phead)
{
  //1.哨兵位头节点不为空
  assert(phead);
  //2.空表不可删(不包含哨兵位的头节点)
  assert(!empty(phead));
  //3.找第一个有效节点(phead->_next)的_next,连接它和phead,释放第一个有效节点
  DLlist* effective_head = phead->_next;
  DLlist* effective_head_next = effective_head->_next;
  phead->_next = effective_head_next;
  effective_head_next->_prev = phead;
  free(effective_head);
}

void insert(PDlist pos,list_val_type val)
{
  //1. pos是不可能为NULL的
  assert(pos);
  //2.造新节点
  DLlist* newnode = build_node(val);
  //3.得到pos->_prev,将这个节点和newnode链接起来
  DLlist* pos_prev = pos->_prev;
  newnode->_next = pos;
  pos->_prev = newnode;
  newnode->_prev = pos_prev;
  pos_prev->_next =newnode;
}

void erase(PDlist pos)
{
  //1.既然是删除,链表一定是一个非空链表,即pos位置一定要合法
  assert(pos);
  //2.找到pos->_prev和pos->_next,连接着两个节点,释放pos即可
  DLlist* pos_prev = pos->_prev;
  DLlist* pos_next = pos->_next;
  pos_prev->_next = pos_next;
  pos_next->_prev = pos_prev;
  free(pos);
}

void destroy(PDlist phead)
{
  //1.哨兵位节点不可能为空
  assert(phead);
  //2. 从phead->_next开始(不包括phead),保存下一个节点,释放当前节点
  DLlist* cur = phead->_next;
  DLlist* next = NULL;
  while(cur != phead)
  {
    next = cur->_next;
    phead->_next = next;
    next->_prev = phead;
    free(cur);
    cur = next;
  }
    free(head);  // 将哨兵位的头节点也干掉
}

 4.单链表的练习题

4.1. 第一题:移除链表元素

203. 移除链表元素 - 力扣(LeetCode)

//方法一: 利用链表的删除
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* cur = head;
        ListNode* prev = nullptr;
        while(cur != nullptr)
        {
            if(cur->val == val)
            {
                //执行删除
                //a. 头删
                if(cur == head)
                {
                    cur = cur->next;
                    delete head;
                    head = cur;
                }
                //b. !头删
                else
                {
                    ListNode* next = cur->next;
                    delete cur;
                    prev->next = next;
                    cur = next;
                }
            }
            else
            {
                //遍历下一个元素
                prev = cur;
                cur = cur->next;
            }
        }
        return head;
    }
};

//方法二:  建立新表
class Solution {
public:
    ListNode* build_node(int val)
    {
        ListNode* newnode = new ListNode;
        newnode->next = nullptr;
        newnode->val = val;
        return newnode;
    }
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* cur = head;
        ListNode* new_head = nullptr;  //新链表
        while(cur != nullptr)
        {
            //不需要删除的节点
            if(cur->val != val)
            {
                //尾插到新链表里
                //a. 空表
                ListNode* newnode = build_node(cur->val);
                if(new_head == nullptr)
                {
                    new_head = newnode;
                }
                //b. !空表
                else
                {
                    // 找尾
                    ListNode* tail = new_head;
                    while(tail->next != nullptr)
                    {
                        tail = tail->next;
                    }
                    tail->next = newnode;
                }
            }
            //遍历下一个节点
            cur = cur->next;
        }
        return new_head;
    }
};

方法三: 同样是建立新表,但是不造新节点
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* cur = head;
        ListNode* cur_next = nullptr;
        ListNode* new_head = nullptr; //新建一个空链表
        while(cur != nullptr)
        {
            // 尾插到新表中
            if(cur->val != val)
            {
                cur_next = cur->next;
                //a. 空表
                if(new_head == nullptr)
                {
                    new_head = cur;
                    new_head->next = nullptr; //在这里需要将尾节点的next置为nullptr
                }
                //b. !空表
                else
                {
                    //找尾
                    ListNode* tail = new_head;
                    while(tail->next != nullptr)
                        tail = tail->next;
                    tail->next = cur;
                    cur->next = nullptr;  //在这里需要将尾节点的next置为nullptr
                }
                cur = cur_next;
            }
            // 遍历下一个
            else
                cur = cur->next;
        }
        ListNode* tail = new_head;
        // 非空表再去找tail
        if(tail != nullptr)
        {
            while(tail->next != nullptr)
                tail = tail->next;
            tail->next = nullptr;    //在这里需要将尾节点的next置为nullptr
        }       
        return new_head;
    }
};

4.2. 第二题:反转链表

206. 反转链表 - 力扣(LeetCode)

struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* n1,*n2,*n3;
    n1 = NULL;
    n2 = head;
    n3 = NULL;
    //利用头插的方式进行反转链表
    while(n2 != NULL)
    {
        n3 = n2->next;
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if(n3)
            n3 = n3->next;
    }
    return n1;
}
# 方案二:同样是利用头插,只不过这需要建立一个新链表
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* new_head = nullptr;
        ListNode* cur = head;
        while(cur != nullptr)
        {
            ListNode* next = cur->next;
            // a. 空表
            if(new_head == nullptr)
            {
                new_head = cur;
                new_head->next = nullptr;
                cur = next;
            }
            // b. !空表
            else
            {
                cur->next = new_head;
                new_head = cur;
                cur = next;
            }
        }
        return new_head;
    }
};

4.3. 第三题:不提供头结点,删除pos

假设现在有一个单链表,没有提供头指针,请删除pos所在的节点,如图所示:

 

void special_erase(list_node* pos)                                                                                                                        
{                                                                                                                                                         
  list_node* pos_next = pos->_next;  # 找到pos的下一个节点                                                                                                                      
  pos_next->_val = pos->_val;    # 赋值                                                                                                 
  pos->_next = pos_next->_next;   # 更新_next域                                                                                                                                         
}

4.4. 第四题:链表的中间节点

876. 链表的中间结点 - 力扣(LeetCode)

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* fast = head,*slow = head;
        //fast为nullptr || fast->next为nullptr 就结束了
        //fast != nullptr && fast->next != nullptr  继续的条件
        while(fast != nullptr && fast->next != nullptr)
        {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
};

4.5. 第五题:合并两个有序链表

21. 合并两个有序链表 - 力扣(LeetCode)

class Solution {
public:
    bool get_min(ListNode* p1,ListNode* p2)
    {
        return p1->val < p2->val;
    }
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr)    return list2;
        if(list2 == nullptr)    return list1;
        ListNode* head1 = list1;
        ListNode* head2 = list2;
        ListNode* new_head = nullptr;
        ListNode* new_tail = nullptr;
        while(head1 != nullptr && head2 != nullptr)
        {
            //a. 头插
            if(new_head == nullptr)
            {
                if(get_min(head1,head2))
                {
                    new_head = new_tail = head1;
                    head1 = head1->next;
                }
                else
                {
                    new_head = new_tail = head2;
                    head2 = head2->next;
                }
            }
            //b. !头插
            else
            {
                if(get_min(head1,head2))
                {
                    new_tail->next = head1;
                    new_tail = head1;
                    head1 = head1->next;
                }
                else
                {
                    new_tail->next = head2;
                    new_tail = head2;
                    head2 = head2->next;
                }
            }
        } 
        // 如果其中一个链表为空,就尾插另一个链表
        if(head1 != nullptr)
            new_tail->next = head1;
        if(head2 != nullptr)
            new_tail->next = head2;
        return new_head;
    }
};
# 方案二,建立一个带哨兵位的新链表
class Solution {
public:
    bool get_min(ListNode* left_node,ListNode* right_node)
    {
        return left_node->val < right_node->val;
    }
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr)
            return list2;
        if(list2 == nullptr)
            return list1;
        ListNode* cur1 = list1;
        ListNode* cur2 = list2;
        ListNode* new_head = nullptr;
        ListNode* new_tail = nullptr;
        new_head = new_tail = new ListNode;
        new_head->next = nullptr;
        while(cur1 && cur2)
        {
            // cur1->val < cur2->val
            //尾插list1的节点
            if(get_min(cur1,cur2))
            {
                new_tail->next = cur1;
                new_tail = cur1;
                cur1 = cur1->next;
            }
            // cur1->val >= cur2->val    
            //尾插list2的节点
            else
            {
                new_tail->next = cur2;
                new_tail = cur2;
                cur2 = cur2->next;
            }
        }
        // 其中一个不为nullptr,就尾插谁
        if(cur1 != nullptr)
            new_tail->next = cur1;
        if(cur2 != nullptr)
            new_tail->next = cur2;
        return new_head->next;
    }
};

4.6. 第六题:链表中倒数第k个节点

链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
		ListNode* fast = pListHead,*slow = pListHead;
		ListNode* cur = pListHead;
		size_t num = 0;
		while(cur != nullptr)
		{
			cur = cur->next;
			num++;
		}
		// 如果k大于链表元素个数,返回nullptr
		if(k > num)
			return nullptr;
		// 正常情况下,fast先走k步
		while(k--)
		{
			fast = fast->next;
		}
		// 同时走,当fast == nullptr时,结束,返回slow
		while(fast)
		{
			fast = fast->next;
			slow = slow->next;
		}
		return slow;
    }
};

4.7. 第七题:链表分割

链表分割_牛客题霸_牛客网 (nowcoder.com)

class Partition {
public:
    bool get_min(ListNode* cmp_node, int val)
    {
        return cmp_node->val < val;
    }
    ListNode* partition(ListNode* pHead, int x) {
        //建立两个带哨兵位的链表:
        // val >= x 的链表
        ListNode* max_head = nullptr,*max_tail = nullptr;
        // val < x 的链表
        ListNode* min_head = nullptr,*min_tail = nullptr;
        min_head = min_tail = new ListNode(0);
        max_head = max_tail = new ListNode(0);
        // 遍历原链表的节点cur
        ListNode* cur = pHead;
        while(cur != nullptr)
        {
            // cur->val < x
            // 尾插到min链表中
            if(get_min(cur,x))
            {
                min_tail->next = cur;
                min_tail = cur;
            }
            // cur->val >= x
            // 尾插到max链表中
            else
            {
                max_tail->next = cur;
                max_tail = cur;
            }
            cur = cur->next;
        }
        min_tail->next = max_head->next;
        //防止成环,因为可能最后一个元素是小于val的
        //那么此时max_tail的next就只想这个元素,构成了一个环
        max_tail->next = nullptr;
        //提前保存min_head->next
        ListNode* obj_node = min_head->next;
        delete min_head;
        delete max_head;
        return obj_node;
    }
};

如果最后不将max_tail->next == nullptr,那么此时有可能成环,如下图所示:

4.8. 第八题:链表回文结构

链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

# 方案一:利用栈的LIFO
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        std::stack<ListNode*> st;
        //1. 找中间节点
        ListNode* mid = A;
        ListNode* slow = A;
        ListNode* fast = A;
        while(fast != nullptr && fast->next != nullptr)
        {
            slow = slow->next;
            fast = fast->next->next;
        }
        mid = slow;
        //2. 得到原表的元素个数
        ListNode* tmp = A;
        size_t cnt = 0;
        while(tmp != nullptr)
        {
            tmp = tmp->next;
            cnt++;
        }
        //3. 入栈
        //a. 奇数个元素,入中间节点
        ListNode* cur = A;
        if(cnt % 2 != 0)
        {
            while(cur != mid->next)
            {
                st.push(cur);
                cur = cur->next;
            }
        }
        else
        //b. 偶数个元素,不入中间节点
        {
            while(cur != mid)
            {
                st.push(cur);
                cur = cur->next;
            }
        }
        //出栈
        //a. 奇数个元素,先出一个节点,这个节点不用判断
        //然后依次比较栈顶数据和链表剩余数据,相等就出
        if(cnt % 2 != 0)
        {
            st.pop();
            cur = cur->next;
            while(!st.empty() && st.top()->val == cur->val)
            {
                st.pop();
                cur = cur->next;
            }
        }
        //b. 偶数个元素
        //依次比较栈顶数据和链表剩余数据,相等就出
        else
        {
            while(!st.empty() && st.top()->val == cur->val)
            {
                st.pop();
                cur = cur->next;
            }
        }
        // 栈为nullptr,就是回文结构
        return st.empty();
    }
};


// 方案二:利用逆置的思想
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        //1. 找到中间节点
        ListNode* mid = nullptr;
        ListNode* fast = A;
        ListNode* slow = A;
        while(fast != nullptr && fast->next != nullptr)
        { 
            slow = slow->next;
            fast = fast->next->next;
        }
        mid = slow;
        //2. 逆置
        //建立新表
        ListNode* cur = mid;
        ListNode* cur_next = nullptr;
        ListNode* new_head = nullptr;
        while(cur != nullptr)
        {
            // 利用头插
            cur_next = cur->next;
            cur->next = new_head;
            new_head = cur;
            cur = cur_next;     
        }
        //3. 比较
        ListNode* cmp_old_head = A;
        ListNode* cmp_new_head = new_head;
        // 有一个结束,就都结束了 
        while(cmp_old_head && cmp_new_head)
        {
            // 只要一个不相等,就返回false
            if(cmp_new_head->val != cmp_old_head->val)
                return false;
            cmp_new_head = cmp_new_head->next;
            cmp_old_head = cmp_old_head->next;
        }      
        return true;
    }
};

 4.9. 第九题:相交链表

160. 相交链表 - 力扣(LeetCode)

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        
        ListNode* cur1 = headA;
        ListNode* cur2 = headB;
        int cnt1 = 1;
        int cnt2 = 1;
        // 得到listA的长度cnt1
        while(cur1->next != nullptr)
        {
            cur1 = cur1->next;
            ++cnt1;
        }
        // 得到listB的长度cnt2
        while(cur2->next != nullptr)
        {
            cur2 = cur2->next;
            ++cnt2;
        }
        // 如果相交,两个链表的尾节点应该一样
        if(cur2 != cur1)
            return nullptr;
        // 如果走到这,说明这两个链表一定会有交点
        
        // 默认listA是long_list,listB是short_list
        // len为两个链表的长度差
        int len = cnt1 - cnt2;
        ListNode* long_list = headA;
        ListNode* short_list = headB;
        // 如果len < 0,更新对应关系
        if(len < 0)
        {
            len *= -1;
            long_list = headB;
            short_list = headA;
        }
        // 长链表先走len步
        while(len--)
        {
            long_list = long_list->next;
        }
        // 然后两个链表一起走,如果相等就为相交节点
        while(long_list != short_list)
        {
            long_list = long_list->next;
            short_list = short_list->next;
        }
        return long_list;
    }
};

4.10. 第十题:环形链表

141. 环形链表 - 力扣(LeetCode)

class Solution {
public:
    bool hasCycle(ListNode *head) {
        //利用快慢指针
        ListNode* slow = head;
        ListNode* fast = head;
        // 如果带环,slow和fast一定会相遇,即带环
        // 当slow进环之后,fast开始追击slow,每追击一次,它们的差值会-1
        // 因为fast走两步,slow走一步,即等于fast走一步,slow不动,因次它们一定会相遇
        // 如果fast和slow走的步数的差值为1,在有环的情况下,一定会相遇。
        while(fast && fast->next)
        {    
            fast = fast->next->next;
            slow = slow->next;
            if(slow == fast)
                return true;
        }
        return false;
    }
};

4.11. 第十一题:环形链表Ⅱ:

142. 环形链表 II - 力扣(LeetCode)

4.11.1. 第一种方法:

 

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
            // 如果有环,一定会相遇
            if(slow == fast)
            {
                // 一个从相遇点走,一个从起始点走,相遇的节点就是入环的第一个节点
                ListNode* meet = slow;
                ListNode* start = head;
                while(meet != start)
                {
                    meet = meet->next;
                    start = start->next;
                }
                return meet;
            }
        }
        //如果没有环,就没有相遇
        return nullptr;
    }
};

4.11.2. 第二种方法:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        //1. 再有环的前提下,找到meet,slow和fast的相交节点
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
            if(fast == slow)
            {
                //得到meet
                ListNode* meet = slow; 
                //2. 断开原链表,将问题转化为两个链表相交的问题
                ListNode* new_head = meet->next;
                meet->next = nullptr;
                //3.解决链表相交,相交点即为入环的第一个节点
                ListNode* tmp1 = head;
                ListNode* tmp2 = new_head;
                int len1 = 0;  //tmp1的长度
                int len2 = 0;  //tmp2的长度
                while(tmp1 != nullptr)
                {
                    tmp1 = tmp1->next;
                    ++len1;
                }
                while(tmp2 != nullptr)
                {
                    tmp2 = tmp2->next;
                    ++len2;
                }
                // 默认old_head是long_list,new_old是short_list
                ListNode* old_head = head;
                int cnt = len1 - len2;
                ListNode* long_list = old_head;
                ListNode* short_list = new_head;
                // 如果cnt<0,需要更新对应关系
                if(cnt < 0)
                {
                    cnt *= -1;
                    long_list = new_head;
                    short_list = old_head;
                }
                // 长链表先走cnt步
                while(cnt--)
                {
                    long_list = long_list->next;
                }
                while(long_list != short_list)
                {
                    long_list = long_list->next;
                    short_list = short_list->next;
                }
                return long_list;
            }
        }
        //如果没环,返回nullptr
        return nullptr;
    }
};

4.12. 第十二题:复制带随即指针的链表

138. 复制带随机指针的链表 - 力扣(LeetCode)

第一步:

第二步:

第三步:

4.12.1. 代码实现: 

class Solution {
public:
    Node* build_node(int val)
    {
        Node* newnode = new Node(val);
        newnode->next = newnode->random = nullptr;
        return newnode;
    }
    Node* copyRandomList(Node* head) {
        if(head == nullptr)
            return nullptr;
        //1. 遍历原表,复制原表节点,尾插到原节点的后面
        Node* cur = head;
        Node* newnode;
        while(cur != nullptr)
        {
            newnode = build_node(cur->val);
            newnode->next = cur->next;
            cur->next = newnode;
            cur = cur->next->next;
        }

        //2. 根据原节点,处理random
        //a. 如果原表中节点的random为nullptr,那么新表中的节点的random也为nullptr
        //b. 根据原节点位置,得到我们需要的random
        cur = head;
        Node* deal_node = cur->next;
        while(cur != nullptr)
        {
            if(cur->random == nullptr)
            {
                deal_node->random = nullptr;
            }
            else
            {
                deal_node->random = cur->random->next;
            }
            cur = deal_node->next;
            if(cur != nullptr)
                deal_node = cur->next;
        }

        // 3. 建立新表,恢复原表
        Node* newhead = nullptr;
        Node* newtail = nullptr;
        cur = head;
        //需要被摘除的节点
        Node* remove_node = cur->next;
        while(cur != nullptr)
        {
            //恢复原表
            cur->next = remove_node->next;
            //处理空表
            if(newhead == nullptr)
            {
                newhead = newtail = remove_node;
                newtail->next = nullptr;
            }
            //处理!空表
            else
            {
                newtail->next = remove_node;
                newtail = remove_node;
                newtail->next = nullptr;
            }
            cur = cur->next;
            if(cur != nullptr)
                remove_node = cur->next;
        }
        return newhead;
    }
};

5. 链表和顺序表的区别:

·注意:我们在这里说的链表说的是带头双向循环链表

不同点顺序表链表
存储空间上
物理上一定连续                        
逻辑上连续,但物理上不一定连续                       
随机访问
支持
不支持
任意位置插入或者删除元素
可能需要移动元素,效率较低,O(N)只需要修改指针的指向,O(1)
插入
动态顺序表,空间不够时需要扩容(也存在空间浪费的问题)
按需申请内存
应用场景
元素高效存储 + 频繁访问
任意位置插入和删除频繁
缓存利用率

如何理解缓存利用率:

在这里有一个问题:既然内存比磁盘快,为什么还需要磁盘?

a. 内存是带电存储,必须通电才能存储数据

b. 内存的单位造价是比磁盘高很多的,而对于普通大众来说,对于性能的要求不是太高,更多的关注的是性价比。

假设我现在要遍历上面的vector和list,而这些数据结构的数据是存储于内存中的,当CPU处理这些数据的时候,是不会直接先去内存中拿数据的(因为CPU也认为内存太慢了),而是CPU先去缓存中找这些所需要的数据,如果找到了,就从缓存里面访问(命中),如果没找到(不命中),就会去内存中将数据加载到缓存,而CPU在加载数据的时候,不会只加载其中一个数据,而是会加载一段数据,例如上面的例子,假如我现在要遍历vector中的1-5的数据,第一次我访问1这个元素的时候,可能不命中,这时候就会将1后面的一段数据全部加载到缓存中(具体为多少,跟CPU有关,我们在这里就简单的认为全部加载到缓存中),那之后我们访问剩下的数据时,就会全部命中(缓存利用率高)。
但是如果是list,由于list只在逻辑上连续,而物理空间是不一定连续的,因此当遍历时,第一个数据不命中后,CPU就会去内存将该数据后的一段数据加载到缓存中,这些加载的空间很有可能就不是我们需要的元素(即不命中,缓存利用率低),同时,这时候也会带来另一个问题,缓存污染。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值