链表

链表

基本概念

  • 链表数据结构):线性表(逻辑结构)的链式存储结构
    • 单链表 VS 顺序表
      • 在单链表上效率更高的操作
        • 删除所有值为x的数
        • 删除某个任意元素
        • 在任意元素后插入一个数
      • 在顺序表上效率更高的操作
        • 在最后一个元素后面插入一个数
        • 交换任意两个元素的值
        • 取出某个元素的值
    • 设置头节点的好处:方便运算的实现
    • 带头结点的双循环链表
      • 链表为空的标志: head->prior==head&&head->next=head
      • 实现删除尾节点和在尾节点后插入的操作效率最高
    • 带尾指针的单循环链表
      • 实现删除头节点和在尾节点后插入的操作效率最高

代码实现

  • 定义

    typedef struct List
    {
    	int val
    	List* next;
        List(int _val):val(_val),next(NULL){
        }
    };
    
  • 链式法

    • 尾插法

      • 带表头

        List* creatlist()
        {
            List* L1=new(List);
            List* L2=new(List);
        /**/List* head=new(List);
        /**/head->next=L1;
            while(1)
            {
                if(/* 终止条件 */) break;
                
                L1->val=val;
                L2->next=L1;
                L2=L1;
                
                L1=new(List);
            }
            L2->next=NULL;//尾节点指向NULL,即代表单链表
            //L2->next=head;尾节点指向头节点,即代表循环链表
             
            return head;
        }
        
      • 无表头:只需将上面的两行/**/标记行改为 List head=L1; 即可

    • 头插法

      • 带表头

        List *creatlist()
        {
        	List *L1 = NULL;
        	List *L2 = NULL;
        	while (1)
            {
        		if (/* 终止条件 */) break;
        		
        		L1 = new (List);
        		L1->val = val;
        		L1->next = L2;
        		L2 = L1;
        	}
        /**/List *head = new (List);
        /**/head->next = L1;
        
        	return head;
        }
        
      • 无表头:只需将上面的两行标记行改为 List head=L1; 即可

    • 总结

      • 整体逻辑:L1和L2两个结点,交替地向后或向前循环建立起整个链表

      • 尾插法vs头插法

        • 尾插法:函数一开始new出来的L1是最终的头节点,所以head=L1写在while循环前
        • 头插法:函数一开始new出来的L1是最终的尾节点,直到while循环结束后,L1才是头节点,所以head=L1写while循环之后
        • 尾插法: L2->next=L1;L2=L1;
        • 头插法: L1->next=L2;L2=L1;
      • 带表头vs无表头

        • 带表头:给表头head分配空间,然后 head->next=L1;
        • 无表头:不给表头head分配空间,直接 head=L1;
      • 单链表vs循环链表

        • 单链表尾:指针指向NULL
        • 循环链表:尾指针指向头结点
  • 数组法

    • 尾插法

      typedef struct List
      {
      	int val;
      	List* next;
      };
      
      int main()
      {
      	int n;
      	cin>>n;
      	List a[500];
      	for(int i=0;i<n;i++)
      	{
      		/* 输入数据 */
      		
      		a[i].next=&a[i+1];
      	}
      	
      	/* 可用sort(a,n,cmp)对链表里的数据进行排序 */
      	
      	for(int i=0;i<n;i++)
      	{
      		a[i].next=&a[i+1];
      	}
      	/* 排序后需要重新建立链式关系 */
      	
      	a[n-1].next=NULL;
      }
      
    • 头插法

      const int N=10010;
      int e[N],ne[N],idx,head=-1;
      
      void insert_head(int x)
      {
      	e[idx]=x;
      	ne[idx]=head;
      	head=idx++;
      }
      //在链表头插入一个节点
      
      void insert(int x,int k)
      {
      	e[idx]=x;
      	ne[idx]=ne[k];
      	ne[k]=idx++;
      }
      //在节点k后插入一个节点
      
      void remove(int k)
      {
      	ne[k]=ne[ne[k]];
      }
      //删除一个节点
      
  • 常用操作

    • 插入

      • step1:new一个插入结点x
      • step2:next[x]=next[k],next[k]=x
    • 删除:next[k]=next[next[k]]

      • 执行删除操作时候的一个小技巧:虚拟头结点

        List *deletenode(List* head,int k)
        {
        	List* newhead=new List(0);
        	newhead->next=head;
        	//在真正的头结点之前设置一个虚拟的头节点
        	//使得需要删除真正的头节点时的处理逻辑,和删除任意结点时的处理逻辑相同
        	
        	return newhead->next;
        	//注意不能返回head,因为有可能head结点是被删除的,而你没有delet head;
        }
        
  • 解题技巧

    • 双指针法:fast指针和slow指针
      • 反转链表:slow=NULL,fast=head,每次让fast—>next=slow,然后往下遍历
      • 倒数第k个结点:fast先走k步,然后slow从head开始跟fast一起走,当fast走到NULL时,slow刚好在倒数第k个结点
    • 循环链表
    • 双向链表
posted @ 2022-11-22 11:30  pinoky  阅读(61)  评论(0编辑  收藏  举报