数据结构之单链表(笔记)

一、顺序表与链表的优缺点

顺序表缺陷:1.空间不够了要增容,增容要付出代码;原地扩容代价低,异地扩容代价高。

2.避免频繁扩容,满了基本都是扩2倍,可能会导致一定的空间浪费。

3.顺序表要求数据从开始位置连续存储,那么我们在头部活中间位置插入删除数据就需要挪动数据,效率不高。

顺序表的优点:支持随机访问,有些算法,需要结构支持随机访问,如:二分查找、优化的快排等。

针对顺序表的缺陷,设计出了链表。

链表的优点:按需申请空间,不用就释放(更合理的使用了空间);头部中间插入删除数据,不需要挪动数据; 不存在空间浪费。

链表的缺点:每一个数据,都要存一个指针去链接后面数据节点;不支持随机访问(用下标直接访问第i个)。

单链表的缺陷还是很多,单纯单链表增删查找的意义不大

1、很多题考察的都是单链表;

2、单链表更多的是去更复杂数据结构的子结构,如哈希桶,邻接表。

链表存储数据还要看双向链表。

二、单链表定义的相关代码

 单链表尾插法

//单链表尾插法
void SListPushBack(SLTNode* phead SLTDateType x)
{
    //找到尾结点
    SLTNode* tail=phead;//从第一个结点开始
    while(tail->next!=NULL)
    {
        tail=tail->next;//找到最后一个结点
    }
    SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));//开辟一个新结点存放x的值
    newnode->data=x;/
    newnode->next=NULL;//newnode存放完x的值后,作为最后一个结点,指向NULL
    tail->next=newnode;//与链表连接
}
//SList.h文件
typedef int SLDateType;
typedef struct SListNode
{
   SLdateType data;
   struct SListNode* next;
}SListNode;
void SListPrint(SLTNode* phead);
void SListPushBack(SLTNode** pphead,SLTDateType x);
void SListPushFront(SLTNode** pphead,SLTDateType x);
void SListpopBack(SLTNode** pphead);
void SListpopFront(SLTNode** pphead);
SLTNode* SListFind(SLTNode* phead,SLTDateType x);
void SListInsert(SLTNode** phead,SLTNode* pos,SLTDateType x);
void SListInsertAfter(SLTNode* pos,SLTDateType x);
void SListErase(SLTNode** phead,SLTNode* pos);
void SListErase(SLTNode** phead,SLTNode* pos);
void SListDestory(SLTNode** phead);
#pragma once
#include 
typedef int SLTDateType;//SLDataType的作用等同于int,当数据的类型要变成char或者double时,只用把int换成char或者double
typedef struct SListNode
{
    SLTDateType data;
    struct SListNode* next;
}SLTNode;
//输出链表中的数据
void SListPrint(SLTNode* phead)
{
    SLTNode* cur=phead;//从第一个结点开始
    while(cur!=NULL)
    {
      printf("%d->",cur->data);//打印数据
      cur=cur->next;//指向下一个结点
    }
}
//尾插法
void SListPushBack(SLTNode** pphead,SLTDateType x)
{
    SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));
    newnode->data=x;
    newnode->next=NULL;//开辟一个数据的空间,把x放进去
    if(*pphead==NULL)
    {
       *pphead=newnode;//链表为空直接插入
    }
    else {
    SLTNode* tail=*pphead;
    while(tail->next!=NULL)
    {
        tail=tail->next;//从头结点开始找,一直找到尾结点
    }
        tail->next=newnode;//把数据插在尾结点的后面
    }
}
//头插法
void SListPushFront(SLTNode** pphead,SLTDateType x)
{
    SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));
    newnode->data=x;//开辟一个空间,存放X的值
    newnode->next=NULL;
    if(newnode==NULL)
    {
      printf("malloc fail\n");//打印数据
      exit(-1);//指向下一个结点
    }
    newnode->next=*pphead;//把x放在头结点的前面
    *pphead=newnode;//x此时的位置作为头结点
}
//尾删法
void SListpopBack(SLTNode** pphead)
{   
    assert(*pphead!=NULL);//不为空
    if((*pphead)->next==NULL)
    {
        free(*pphead);
        *pphead=NULL;//只有一个数据时,删除这个数据
    }
    SLTNode* pre=NULL;
    SLTNode* tail=*pphead;
    while(tail->next)
    {
    pre=tail;
    tail=tail->next;//从头结点开始,一直向后,到尾结点,pre是tail的前一个结点
    }
    free(tail);//释放tail空间
    tail=NULL;
    pre->next=NULL;
}
//头删法
void SListpopFront(SLTNode** pphead)
{
    assert(*pphead!=NULL);//不为空
    if((*pphead)->next==NULL)
    {
        free(*pphead);//只有一个数据时,释放这个数据
        *pphead=NULL;
    }
    else 
    {
    SLTNode* pre=*pphead;
    *pphead=(*pphead)->next;
    free(pre);    
    }
}
//找数据
SLTNode* SListFind(SLTNode* phead,SLTDateType x)
{
    SLTNode* cur=phead;
    while(cur)
    {
     if(cur->data==x)
     {
         return cur;//找到x的位置,返回
     }   
     else 
     {
         cur=cur->next;//找不到x,一直往下走
     }
    }
    return NULL;
}
//在pos位置前插入数据
void SListInsert(SLTNode** phead,SLTNode* pos,SLTDateType x)
{
    SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));
    newnode->data=x;
    newnode->next=NULL;//开辟一个数据的空间,把x放进去
    SLTNode* pre=*phead;
    while(pre->next!=pos)
    {
    pre=pre->next;//找到pos前一个位置
    }
    pre->next=newnode;
    newnode->next=pos;
}//复杂度O(N)
//在pos位置后插入数据
void SListInsertAfter(SLTNode* pos,SLTDateType x)
{
    SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));
    newnode->data=x;
    newnode->next=NULL;//开辟一个数据的空间,把x放进去
    newnode->next=pos->next;
    pos->next=newnode;   
}//复杂度O(1)
//删除结点
void SListErase(SLTNode** phead,SLTNode* pos)
{
    SLTNode* pre=*phead;
    if(*phead==pos)
    {    
    *phead=pos->next;//让链表第二个变成头
    free(pos);//释放掉第一个结点
    }
    else
    {
     while(pre->next!=pos)
     {
     pre=pre->next;     
     }
     pre->next=pos->next;//pos的上一个结点指向pos的下一个结点
     free(pos);
    }      
}
//删除该结点之后的结点
void SListEraseAfter(SLTNode** phead,SLTNode* pos)
{
    assert(pos->next);
    SLTNode* next=pos->next;
    pos->next=next->next;
    free(next);
}
//删除链表
void SListDestory(SLTNode** phead)
{
    assert(phead);
    SLTNode* cur=*phead;
    while(cur)
    {
    SLTNode* next=cur->next;//不到尾就一直删
    free(cur);
    cur=next;
    }
    *phead=NULL;
}
int main(void) 
{    
    return 0;
}

数据结构之单链表(笔记)_第1张图片

三、单链表部分例题

1.移除链表元素(力扣)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val){
         if(head==NULL)
     {
       return NULL;
   }
    struct ListNode* pre=NULL;
    struct ListNode*cur=head;
    
    while(cur)//cur不为空,一直往后走
    {
    if(cur->val==val)
    {  
      if(head->val==val)//如果头部等于val,头删
    {
    head=cur->next;//让链表第二个变成头
    free(cur);//释放掉第一个结点
    cur=head;//cur等于当前的头
    }  
    else
    {
    pre->next=cur->next;//头部数据不等于val,但找到了数据为val的位置,让pre的下一个指向cur的下一个结点值
    free(cur);//释放掉cur结点的值
    cur=pre->next; //让cur指向pre的下一个位置   
    }
    }
    else
    {
        pre=cur;//没有找到数据为val的位置
        cur=cur->next;//继续往后走
    }
    }
      return head;//返回新的头
}

2.反转链表(力扣)

数据结构之单链表(笔记)_第2张图片

①指针翻转

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head){
if(head==NULL)
{
  return NULL;
}
struct ListNode* n1,*n2,*n3;
n1=NULL;
n2=head;//n2一开始指向头结点
n3=head->next;//n3一开始指向第二个结点
while(n2)
{
    //翻转
    n2->next=n1;//第一个结点指向n1即空
    //迭代走
    n1=n2;//原来的第一个结点变为n1
    n2=n3;//原来的第二个结点变为n2
    if(n3)
    n3=n3->next;//n3变为原来n3的下一个
}
return n1;
}

② 头插(取原链表中结点,头插到newhead新链表中)

数据结构之单链表(笔记)_第3张图片

数据结构之单链表(笔记)_第4张图片

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* cur=head;
struct ListNode* newhead=NULL;
while(cur)
{
   struct ListNode* next=cur->next; 
   //头插
   cur->next=newhead;
   newhead=cur;
   cur=next;
}
return newhead;
}

3.找到链表的中间结点(力扣)

/**
*Definition for singly-linked list.
*struct ListNode{
    int val;
    struct ListNode *next;
*};
*/
//快慢指针,慢指针一次走一个结点,快指针一次走两个结点。当链表节点数为奇数时,快指针走完链表的时候,慢指针恰好走到中间;当为偶数时,快指针走到空时,慢指针走到中间
struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* slow,*fast;
    slow=fast=head;
    while(fast&&fast->next)//当fast为空或者fast->next为空时结束
    {
    slow=slow->next;//slow一次走一个结点
    fast=fast->next->next;//fast一次走两个结点
    }
    return slow;
}

4.返回链表的倒数第k个结点(牛客)

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param head ListNode类 
 * @param n int整型 
 * @return ListNode类
 */
struct ListNode* removeNthFromEnd(struct ListNode* head, int k ) {
struct ListNode* fast,*slow;
slow=fast=head;
while(k--)//......k
//while(--k)......k-1
//fast先走k步,slow再走,当fast走到尾时,slow走到倒数第k个
{
    //k大于链表的长度,返回空
    if(fast==NULL)
    {
    return NULL;
    }
    fast=fast->next;//fast走k步
}
while(fast)
{
    fast=fast->next;
    slow=slow->next;//fast走到k之后fast和slow一起走,直到fast走到空
}
return slow;
}

 5.合并两个有序链表(力扣)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
//其中一个链表为空,返回另外一个
if(l1==NULL)
{
    return l2;
}
if(l2==NULL)
{
    return l1;
}
struct ListNode* head=NULL,*tail=NULL;
while(l1&&l2)//只要有一个为空就停止
{
   if(l1->valval)
   {
       if(head==NULL)//第一次尾插
       {
           head=tail=l1;//把l1插到尾
       }
       else
       {
           tail->next=l1;//不是尾,插到尾
           tail=l1;//l1变为尾
       }
       l1=l1->next;//l1往后走
   }
   else
   {
       if(head==NULL)
       {
           head=tail=l2;
       }
       else
       {
           tail->next=l2;
           tail=l2;
       }
       l2=l2->next;//l2小,l2小往后走
   }
}
if(l1)
{
    tail->next=l1;//l2结束,把l1链接到tail后面
}
if(l2)
{
    tail->next=l2;//l1结束,把l2链接到tail后面
}
return head;
}

6.链表的分割(力扣)

                 (a)  数据结构之单链表(笔记)_第5张图片                    数据结构之单链表(笔记)_第6张图片      (b)

                    (c)    数据结构之单链表(笔记)_第7张图片                  数据结构之单链表(笔记)_第8张图片       (d)  

(a)(b)(c)(d)表示链表数据的整个存放过程,下面这个图则表示将两个链表合并起来,即将lessTail->next指向greatHead->next。

数据结构之单链表(笔记)_第9张图片

如果greatTail不指向空的话,就会出现如下所示的死循环 ,因此greatTail->next=NULL。

数据结构之单链表(笔记)_第10张图片

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* partition(struct ListNode* head, int x){
struct ListNode* lessHead,*lessTail,* greatHead,*greatTail;
//开一个哨兵位头结点,方便尾插,先分为两个链表,less里存放比X小的值,great存放比X大的值
lessHead=lessTail=(struct ListNode*)malloc(sizeof(struct ListNode));
lessTail->next=NULL;
greatHead=greatTail=(struct ListNode*)malloc(sizeof(struct ListNode));
greatTail->next=NULL;
struct ListNode* cur=head;
while(cur)
{
   if(cur->valnext=cur;//值比X小,放在less里
      lessTail=cur;//cur变成队尾
   }
   else
   {
      greatTail->next=cur;//值比X大,放在great里
      greatTail=cur;//cur变成队尾
   }
   cur=cur->next;//指向下一个
}

   lessTail->next=greatHead->next;//合并两个链表
   greatTail->next=NULL;//防止出现死循环
   struct ListNode* newhead=lessHead->next;//保存头结点
   free(lessHead);
   free(greatHead);
   return newhead;
} 

7.相交链表(牛客网)

判断两个链表是否相交;如果相交,求交点。

①暴力求解(穷举):依次取A链表中的每个结点跟B链表中的所有结点比较,如果有地址相同的结点,就是相交,第一个相同的交点。

②要求优化到O(n):尾结点相同就是相交,否则就不相交;长的链表先走(长度差步),再同时走,第一个相同就是交点。

 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
 {
     struct ListNode* tailA=headA;
     struct ListNode* tailB=headB;
     int lenA=1;
     while(tailA->next)
     {   lenA++;//算出链表A的长度
         tailA=tailA->next;//找到A的尾
     } 
     int lenB=1;
     while(tailB->next)
     {
         lenB++;//算出链表B的长度
         tailB=tailB->next;//找到B的尾
     } 
     if(tailA!=tailB)//不相交
     {
         return NULL;
     }
     int gap=abs(lenA-lenB);
     //长的先走差距步,再同时走找交点
     struct ListNode* longList=headA;
     struct ListNode* shortList=headB;
     if(lenAnext;   
     }
     while(longList!=shortList)
     {
       longList=longList->next;
       shortList=shortList->next; //没有相交的,两个链表就往后走
     }
     return longList;
 }

8.复制带随机指针的链表(力扣)

① 复制结点,插入到原结点和下一个结点之间;

② 根据原结点random,处理复制结点的random;

③ 复制结点解下来链接成一个新链表,恢复原链表链接关系。

数据结构之单链表(笔记)_第11张图片

数据结构之单链表(笔记)_第12张图片

数据结构之单链表(笔记)_第13张图片

struct Node* copyRandomList(struct Node* head) {
    //1.拷贝结点插入原结点的后面
    struct Node* cur=head;
    while(cur)
    {
       struct Node* copy=(struct Node*)malloc(sizeof(struct Node)); 
       copy->val=cur->val;//copy的值等于原结点的值
       //插入copy结点
       copy->next=cur->next;//第二个图
       cur->next=copy;
       cur=copy->next;//第三个图
    }
	//2.根据原结点,处理copy结点的random
    cur=head;
    while(cur)
    {
        struct Node* copy=cur->next;
        if(cur->random==NULL)
        {
            copy->random==NULL;
        }
        else
        {
            copy->random=cur->random->next;
        }
        cur=copy->next;
    }
    //3.把拷贝结点解下来,链接成新链表,同时恢复原链表。
    struct Node* copyHead=NULL,*copyTail=NULL;
    cur=head;
    while(cur)
    {
        struct Node* copy=cur->next;
        struct Node* next=copy->next;
        if(copyTail==NULL)
        {
            copyHead=copyTail=copy;//头插
        }
        else
        {
            copyTail->next=copy;//尾插
            copyTail=copy;//copyTail往后走
        }
        cur->next=next;//恢复原链表
        cur=next;
    }
    return copyHead;
}

你可能感兴趣的:(数据结构,数据结构,c语言,链表)