数据结构与算法1-链表篇

数据结构与算法-链表篇

往期内容
1-链表
2-栈与队列
3-树与图
4-哈希表
5-查找
6-排序
7-贪心
8-递归与分治
9-动态规划



基本概念

线性表:是由零个或者多个元素组成的有限序列


一、线性表的存储结构

线性表的存储结构有两种:线性顺序结构和链式存储结构

1.1 顺序存储结构

//定义顺序存储线性表
#define MAXSIZE 20//定义最大的元素个数
typedef int ElemType;//定义数据的类型
struct SqList
{
    ElemType data[MAXSIZE];
    int length;//定义线性表的当前长度
};

1.2 链式存储结构

//单链表的存储结构
typedef int ElemType;
struct Node
{
    ElemType data;//数据域
    Node *Next;//指针域
};
//定义节点的指针
typedef Node* LinkList;

1.3 双向链表

typedef int ElemType;
//2.双向链表
typedef struct  DulNode
{
    ElemType data;
    DulNode *prior;//指向前驱指针
    DulNode *next;//指向后继指针
}DulNode,*DuLinkList;

二、基本操作

2.1 获取元素

顺序存储结构 (一定要注意边界条件)

/*
获取元素操作:bool GetElem(SqList L,int i,ElemType *e)
功能:在线性表L中,将第i个位置的值,传递给e
      如果传递成功返回True,失败返回False
*/
bool GetElem(SqList L,int i,ElemType *e)
{
    //防止越界
    if(L.length==0 || L.length>MAXSIZE || i<1)
    {
        return false;
    }
    *e=L.data[i-1];
    return true;
}

链式存储结构 (注意第二个判断条件)

/*
获取元素操作:bool GetElem(LinkList L,int i,ElemType *e)
功能:在线性表L中,将第i个位置的值,传递给e
      如果传递成功返回True,失败返回False
*/
bool GetElem(LinkList L,int i,ElemType *e)
{
    //假设存在头节点
    LinkList p=L->Next;//p指向第一个节点
    int j=1;
    while(p && j<i)
    {
        p=p->Next;
        ++j;
    }
    if(!p || j>i)//越界:1.链表为空,2.i的值小于0
    {
        return false;
    }
    *e=p->data;//获取第i个位置的值
    return true;
}

2.2 插入操作

顺序存储

/*
插入操作:bool ListInsert(SqList *L,int i,ElemType e)
功能:在线性表L的i位置插入数值e,并返回bool类型的标志,判断是否插入成功
假设从插入点开始,所有元素向后移动
*/
bool ListInsert(SqList *L,int i,ElemType e){
    //判断是否越界
    if(L->length==MAXSIZE)//判断顺序线性表是否满
    {
        return false;
    }
    if(i<1|| i>L->length+1)
    {
        return false;
    }
    if(i<L->length)//如果不在表尾,其他元素需要空出当前的位置
    {
        for(int k=L->length-1;k>i-1;k--)//从当前的位置k的值,依次向后移动
        {
            L->data[k+1]=L->data[k];//要从末尾的后一位开始,借位然后从后往前移动。错误写法:L->data[k]=L->data[k-1];
        }
    }
    L->data[i-1]=e;
    L->length++;
    return true;
}

链式存储

/*
插入操作:bool ListInsert(LinkList *L,int i,ElemType e)
功能:在线性表L的i位置插入数值e,并返回bool类型的标志,判断是否插入成功
假设从插入点开始,所有元素向后移动
*/
bool ListInsert(LinkList *L,int i,ElemType e){
    LinkList p,s;
    p=*L;
    int j=1;
    while(p && j<i)
    {
        p=p->Next;
        ++j;
    }
    if (!p || j>i)
    {
        return false;
    }//p指向i-1的位置

    //申请内存空间
    s=(LinkList)malloc(sizeof(Node));
    s->data=e;

    s->Next=p->Next;
    p->Next=s;

    return true;
}

2.3 删除操作

顺序存储 (除最后一个位置,其他位置都需要移动元素)

/*
删除操作:bool LisDelete(SqList *L,int i,ElemType *e)
功能:在L顺序线性表中的,第i个位数,删除元素,并将删除的值存储在(*e)中,并返回bool类型的标志,判断是否删除成功
*/
bool LisDelete(SqList *L,int i,ElemType *e)
{

    if(L->length==0)//切记,不要忘记此条件
    {
        return false;
    }
    if(i<1 && i>L->length)
    {
        return false;
    }
    *e=L->data[i-1];
    if(i<L->length)
    {
        for(int k=i-1;k<L->length;k++)
        {
            L->data[k]=L->data[k+1];
        }
    }
    L->length--;
    return false;
}

链式存储

/*
删除操作:bool LisDelete(LinkList *L,int i,ElemType *e)
功能:在L线性表中的,第i个位数,删除元素,并将删除的值存储在(*e)中,并返回bool类型的标志,判断是否删除成功
*/
bool LisDelete(LinkList *L,int i,ElemType *e)
{
    LinkList p,s;
    p=*L;//头节点


    int j=1;
    while(p && j<i)
    {
        p=p->Next;
        j++;
    }
    if(!(p) || j>i)
    {
        return false;
    }//指向第i-1个元素

    s=p->Next;
    p->Next=s->Next;
    *e=s->data;

    free(s);//回收释放内存

    return true;
}

2.4 链式表的头插法与尾插法

头插法较为简单,尾插法需要额外的指针,记录尾结点

//头结点指针:数据域为空的结点:先用malloc函数生成结点,在定义next指针域为空即可,数据用不用赋值。
/*
单链表整表的创建1:头插法
随机产生n个元素的值,建立单线性链表L
创建单线性链表:void createListHead(LinkList *L,int n)
n表示线性表的长度
*/
void createListHead(LinkList *L,int n)
{
    LinkList p;
    int i;

    //定义一个随机数种子
    //srand(time(0));

    *L=(LinkList)malloc(sizeof(Node));
    (*L)->Next=NULL;//先建立一个带有头节点的空单链表

    for(int i=0;i<n;i++)
    {
        //创建节点,申请内存
        p=(LinkList)malloc(sizeof(Node));
        p->data=i;//输入数据


        //链接各个结点即可
        p->Next=(*L)->Next;//当前节点指向头节点的原来节点,相当于在头节点和原来节点之间插入了当前节点
        (*L)->Next=p;//表头指向当前节点
    }
}




//先使用malloc函数生成一个头,中间执行循环的结点链接,在函数的最后一句中,将尾结点的next指针指向null
/*
单链表整表的创建2:尾插法
随机产生n个元素的值,建立单线性链表L
创建单线性链表:void createListTail(LinkList *L,int n)
n表示线性表的长度
*/
void createListTail(LinkList *L,int n)
{
    LinkList p,s;
    int i;

    //定义一个随机数种子
    //srand(time(0));
    *L=(LinkList)malloc(sizeof(Node));
    //(*L)->Next=NULL;//先建立一个带有头节点的空单链表

    p=*L;

    for(int i=0;i<n;i++)
    {
        //创建节点,申请内存
        s=(LinkList)malloc(sizeof(Node));
        s->data=i;//输入数据

        p->Next=s;
        //s->Next=NULL;
        p=s;
    }

    s->Next=NULL;//尾结点为空
}

2.5 链式整表删除

/*
单链表的整表删除:
*/
bool ClearList(LinkList *L)
{
    LinkList p,s;
    p=(*L)->Next;//指向第一个结点

    //回收结点指针
    while(p)
    {
        s=p->Next;
        free(p);
        p=s;
    }
    
    //回收头指针
    (*L)->Next=NULL;

    return true;
}

三、Leetcode刷题

2.1 构建

结构

struct ListNode{
	int val;
	ListNode *next;
};

简单的构建方法

	ListNode a(1);
	ListNode b(4);
	ListNode c(6);
	ListNode d(0);
	ListNode e(5);
	ListNode f(7);
	a.next = &b;
	b.next = &c;
	d.next = &e;
	e.next = &f;
	f.next=nullptr;
	ListNode *head=&a;//头结点指针

2.2 反转链表

leetcode 206-反转链表
思路:双指针,一个指向反转链的头结点 (初始化为null,正好形成兑成),另一个记录头结点的下一结点。(头指针,头指针的前位置,头指针下一位置)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        //双指针
        ListNode * new_head=nullptr;
        ListNode * next=nullptr;
        while(head)
        {
            next=head->next;
            head->next=new_head;
            new_head=head;
            head=next;
        }

        return new_head;
    }
};

2.3 反转链表2

leetcode 92-反转链表
思路:1.注意四个位置结点的保存,2.注意返回的头结点 3.用while的时候要注意++

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode *l1=nullptr;//反转常用步骤
        ListNode *l2=head;
        for(int i=1;i<left;i++){
            l1=l2;
            l2=l2->next;
        }

        ListNode *r1=nullptr;//反转常用步骤
        ListNode *r2=l2;    
        ListNode *p=nullptr;//临时next结点
        for(int i=left;i<=right;i++){
            p=r2->next;
            r2->next=r1;
            r1=r2;     
            r2=p;       
        }

        if(l1==nullptr){//头结点特殊处理
            head=r1;
        }else{
            l1->next=r1;
        }
        l2->next=r2;

        return head;
    }
};

方法2:头插法

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode dumpmyHead(-1);
        dumpmyHead.next=head;

        ListNode *l1=&dumpmyHead,*l2=head;
        for(int i=1;i<left;i++){
            l1=l2;
            l2=l2->next;
        }

        for(int i=left;i<right;i++){
            ListNode *p=l2->next;
            l2->next=p->next;
            p->next=l1->next;
            l1->next=p;
        }

        return dumpmyHead.next;

    }
};

2.2,2.3两题总结:反转链表的时候,需要有三个指针,分别保存当前结点前驱的地址(初始化为nullptr),当前结点地址(默认为头指针地址)和后继的地址(避免丢失),

2.4 求两个链表的交点

leetcode 160-相交链表
思路1:暴力解法,定义两个vector容器,分别遍历链表a和b,将所有的结点分别存入对应的容器中,然后从后往前遍历,知道找到不相等的结点为止,返回之前保存的结点。(注意:1.写’;’,2.用while循环里面记得写++操作,不然导致死循环)

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        vector<ListNode *> vec_A;
        vector<ListNode *> vec_B;

        ListNode *p=headA;
        while(p)
        {
            vec_A.push_back(p);
            p=p->next;
        }
        p=headB;
        while(p)
        {
            vec_B.push_back(p);
            p=p->next;
        }

        int i=vec_A.size()-1;
        int j=vec_B.size()-1;
        p=nullptr;
        while(i>=0 && j>=0 && vec_A[i]==vec_B[j])
        {
            p=vec_A[i];
            i--;
            j--;
        }
        return p;
    }
};

思路2:首先遍历A链表的所有结点,并将A链表中的所有结点保存在一个set集合中,然后遍历B中的所有结点,每次遍历的时候检查该结点是否在set集合中 ,如果在返回该结点,否则返回nullptr (注意:set的添加元素的操作是insert,并非push_back,另外set有自带find方法,vector没有自带的find方法)

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        //使用set集合,找到第一个相同的指针地址
        set<ListNode *> node_set;
        ListNode *pnode=headA;
        while(pnode)
        {
            node_set.insert(pnode);
            pnode=pnode->next;
        }

        pnode=headB;
        while(pnode)
        {
            if(node_set.find(pnode)!=node_set.end())
            {
                return pnode;
            }
            pnode=pnode->next;
        }

        return nullptr;
    }
};

思路三:由于求公共的部分,可以先将链表对齐,即先从较长的链表遍历到较短链表的位置,然后同步遍历,直到找到相同的公共位置。备注:空间复杂度为O(1)

class Solution {
    int getListLen(ListNode *head)//求数组的长度
    {
        int len=0;
        while(head)
        {
            len++;
            head=head->next;
        }
        return len;
    }
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int lenA=getListLen(headA);
        int lenB=getListLen(headB);

        if(lenA>lenB)
        {
           for(int i=0;i<lenA-lenB;i++)
           {
               headA=headA->next;
           }
        }
        else
        {
            for(int i=0;i<lenB-lenA;i++)
           {
               headB=headB->next;
           }
        }

        while(headA && headB)
        {
            if(headA==headB)
                return headA;
            headA=headA->next;
            headB=headB->next;
        }
        return NULL;
    }
};

2.5链表求环

Leetcode-141 环形链表
Leetcode-142 环形链表2
思路1:集合的思想set,每遍历一个结点,先判断是否在集合中,如果没有将其加入到set集合中,否则是环形链表,当遍历到空结点时,返回否。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *nodePtr=head;
        set<ListNode *> nodeSet;

        while(nodePtr)
        {
            if(nodeSet.find(nodePtr)!=nodeSet.end())//集合中是否有结点
            {
                return true;
            }
            nodeSet.insert(nodePtr);
            nodePtr=nodePtr->next;
        }
        return false;
    }
};

思路2:快慢指针,用两个指针分别指向头结点,一个走一步,另一个走两步,快指针追上慢指针,是环形链表,快指针走到空结点,不是环形链表

class Solution {
public:
    bool hasCycle(ListNode *head) {
            ListNode *fast=head;//快
            ListNode *slow=head;//慢

            while(fast)
            {
                fast=fast->next;
                slow=slow->next;
                if(fast==nullptr)
                {
                    break;
                }
                fast=fast->next;
                if(fast==slow)
                    return true;
            }
            return false;
    }
};

环形链表2

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        //快慢指针的方法,套圈的方法 a+b+c+b=2*(a+b) 因此a=c,从a和c点同时出发,相遇点是环的入口
        ListNode * fast=head;
        ListNode * slow=head;
        ListNode * meet=nullptr;

        while(fast)
        {
            //先走一步
            fast=fast->next;
            slow=slow->next;
            if(!fast)
                return nullptr;
            fast=fast->next;//快指针走两步

            if(fast==slow)
            {
                meet=fast;
                break;//一定要记得跳出循环
            }
        }

        while(meet && head)
        {
            if(meet==head)
                return head;
            meet=meet->next;
            head=head->next;
        }

        return nullptr;

    }
};

2.6 分割链表

Leetcode-86 分割链表
思路:新建两个空的头指针,比较每个结点的元素,使用尾插法,向两个链表中插入对应的元素,拼接数据,并将尾部指针置为nullptr,== (如果有nodePtr->next操作在等号的左边,记得备份指向的数据)==

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode small_head(0);
        ListNode big_head(0);
        //尾差法-尾指针
        ListNode *small_end_ptr=&small_head;
        ListNode *big_end_ptr=&big_head;

        //移动指针
        ListNode *nodePtr=head;

        while(nodePtr)
        {
            if(nodePtr->val<x)
            {
                small_end_ptr->next=nodePtr;//尾插数据
                small_end_ptr=nodePtr;
            }
            else
            {
                big_end_ptr->next=nodePtr;
                big_end_ptr=nodePtr;
            }
            nodePtr=nodePtr->next;
        }
        small_end_ptr->next=big_head.next;
        big_end_ptr->next=nullptr;

        return small_head.next;
    }
};

2.7合并两个有序链表

Leetcode-24 合并两个有序链表
思路:创建一个空的头结点,分别遍历两个链表并以此加入到新的链表中,由于需要从小到大的排序,因此采用尾插法的 插入方法,需要新的指针记录尾部结点的位置,最后还需要将尾部结点的next指针域指向NULL;
巧用头空指针

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode new_head(0);
        ListNode *lastPtr=&new_head;

        while(l1 && l2)
        {
            if(l1->val <l2->val)
            {
                //l1加入
                lastPtr->next=l1;
                lastPtr=l1;
                l1=l1->next;
            }
            else
            {
                lastPtr->next=l2;
                lastPtr=l2;
                l2=l2->next;
            }
        }
        if(l1)
        {
            lastPtr->next=l1;
        }
        else
        {
            lastPtr->next=l2;
        }

        return new_head.next;
    }
};

2.8 合并K个升序链表

Leetcode-23合并K个升序链表
思路:将lists中链表合并的问题,划分为前一半和后一半合并的问题,在接着划分子问题,知道划分到链表两两合并的问题,分而治之。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {//链表两两合并的代码
        ListNode new_head(0);
        ListNode *lastPtr=&new_head;

        while(l1 && l2)
        {
            if(l1->val <l2->val)
            {
                //l1加入
                lastPtr->next=l1;
                lastPtr=l1;
                l1=l1->next;
            }
            else
            {
                lastPtr->next=l2;
                lastPtr=l2;
                l2=l2->next;
            }
        }
        if(l1)
        {
            lastPtr->next=l1;
        }
        else
        {
            lastPtr->next=l2;
        }

        return new_head.next;
    }


    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size()==0)
            return nullptr;
        if(lists.size()==1)
            return lists[0];
        if(lists.size()==2)
            return mergeTwoLists(lists[0],lists[1]);
        
        int mid=lists.size()/2;

        vector<ListNode *> sub1_lists;
        vector<ListNode *> sub2_lists;

        for(int i=0;i<mid;i++)
        {
            sub1_lists.push_back(lists[i]);
        }
        for(int i=mid;i<lists.size();i++)
        {
            sub2_lists.push_back(lists[i]);
        }

        ListNode *l1=mergeKLists(sub1_lists);//前半部分:分治1
        ListNode *l2=mergeKLists(sub2_lists);//后半部分:分治2

        return mergeTwoLists(l1,l2);
    }
};

加油

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值