数据结构算法——单链表及其操作

来源:互联网 发布:学数控编程用什么软件 编辑:程序博客网 时间:2024/06/06 01:09

单链表是一种非常常用的数据结构,虽然很简单,但是其相关操作还是很容易出错。本文将介绍单链表的几种操作,主要包括链表的反转,链表的排序,求出链表倒数第 k 个值,删除当前节点和找出链表的中间节点。
更多内容可参考如下链接:

https://www.61mon.com/index.php/archives/179/


单链表节点结构

定义单链表的节点结构如下:

/* 单链表节点结构 */struct Node{    int data;    Node * next;    Node() { data = 0; next = nullptr; }};/* 定义头节点 */Node * header = new Node;

链表反转

链表反转是面试笔试中非常常见的题型,需要注意的是反转过程中不能出现中断的情况。
示例:1->2->3->4->5; 反转后:5->4->3->2->1
反转过程中两个指针同步移动,具体代码如下:

/* 反转链表 */void reverse(Node * header){    if(!header->next || !header->next->next)//空链表或只有一个节点        return;    Node* cur = header->next;//指向链表第一个节点    Node* pre = nullptr;    while(cur)    {        Node* tmp = cur->next;//保存下一节点        cur->next = pre;//更改节点指向        pre = cur;//pre向前移动一步        cur = tmp;//cur向前移动一步    }    header->next = pre;//头节点指向反转后的第一个节点}

链表的排序

排序采用快速排序的思想,只需要设置两个指针 i 和 j,这两个指针均往 next 方向移动,移动的过程中始终保持区间 [1, i] 的 data 都小于 base(位置 0 是主元),区间 [i+1, j) 的 data 都大于等于 base,那么当 j 走到末尾的时候便完成了一次支点的寻找。

/** * 链表升序排序 *  * begin 链表的第一个结点,即 header->next * end   链表的最后一个结点的 next */void asc_sort(Node * begin, Node * end){    if(!begin || !begin->next)// 链表为空或只有一个节点        return;    int base = begin->data;// 设置主元    Node* i = begin;       // i 左边的小于 base    Node* j = begin->next; // i 和 j 中间的大于 base    while(j!=end)    {        if(j->data<base)        {            i = i->next;            swap(i->data, j->data);        }        swap(i->data, begin->data);  // 交换主元和 i 的值    }    //    asc_sort(begin, i);     // 递归左边    asc_sort(i->next, end); // 递归右边}//使用方式asc_sort(header->next, nullptr);

求出链表倒数第 k 个值

由于链表长度未知,如果从头扫描一遍链表后得到链表长度,然后再从头找到倒数第k个数,这种方法最直观,但是时间复杂度较高。我们假设 k 小于等于链表长度,那么我们可以设置两个指针 p 和 q,这两个指针在链表里的距离就是 k,那么后面那个指针走到链表末尾的 nullptr 时,另一个指针肯定指向链表倒数第 k 个值。

/* 返回链表倒数第k个值 */int kth_last(Node * header, int k){    Node* p = header->next;    Node* q = header->next;    for(int i=0;i<k;i++)    {        if(!q)            return -1; //链表长度小于k        q = q->next;    }    while(q)    {        p = p->next;        q = q->next;    }    return p->data;}

删除当前节点

要求删除节点的时间平均复杂度为O(1),这样就不能通过扫描链表获取当前节点的前一个节点的方法了。既然指针不能从当前节点的上一个节点指向当前节点的下一个节点,那么就改变当前节点的值为下一个节点的值,同时将下一个节点删除。需要注意的是删除最后一个节点的情况,因为没有下一个节点,故需要找到上一个节点,同样需要扫描链表。

/* 删除当前结点 */void del(Node * header, Node * position){    if(!position->next)  // 要删除的是最后一个节点    {        Node* p = header;        while(p->next!=position)            p = p->next;    // 找到 position 的前一个结点        p->next = nullptr;        delete position;    }    else    {        Node* p = position->next;        swap(position->data, p->data);        position->next = p->next;        delete p;    }}

找出链表的中间节点

通过寻找倒数第k个数的操作后,我们可以想到用类似的两个指针进行操作,这里就采用快慢指针,慢指针走的长度等于快慢指针相距的程度,所以利用这个性质,当快指针走到链表尾时,慢指针正好在中间结点。

/* 找出单链表的中间结点 */Node * find_middle(Node * header){    Node* p = header;    Node* q = p;    while(q->next->next && p->next)    {        p = p->next;        // 慢指针走一步        q = q->next->next;  // 快指针走两步    }    return p;}
原创粉丝点击