什么?还有人学不会链表排序??!(链表插入排序、选择排序、冒泡排序、快速排序)

目录

前言

插入排序

数组插入排序

链表插入排序

找结点

找位置

选择排序

数组选择排序

链表选择排序

定位置

找结点

冒泡排序

数组冒泡排序

链表冒泡排序

记轮数

两两比较

快速排序

数组快速排序

​编辑链表快速排序

分区间

结点分类


前言

排序可以说是每一个计算机爱好者最先接触的算法之一,在C语言中,我们最先是通过数组来学习排序的,由于数组可以直接通过下标对数据进行操作,为我们理解排序算法和实现排序操作提供了许多便利。那么当我们需要对链表进行排序时又该怎么操作呢?下面整理了四种链表排序的思路和具体的代码,在每种链表排序之前,会先复习一下数组中这种排序是如何实现的哦!

插入排序

什么是插入排序?简单来说,就是将每一个元素依次找到它应该插入的位置,当每一个元素都被插入到了它应该被插入的位置时,整个表就完成了排序。

数组插入排序

假设第一个元素为有序表,从第二个元素开始依次查找每个元素应该插入的位置,但是如果该元素本身就符合顺序,则不用调整该元素的位置。这里演示一下从小到大做插排。

第二个元素比第一个元素小,将-3提到4之前

换位完成后,调整第三个元素。第三个元素为7,符合从小到大的顺序,不用调整。

然后是最后一个元素5,比4大且比7小,所以要插入到7之前。

链表插入排序

链表的插入排序与数组的思想类似,找到需要换位的结点,再寻找该结点要插入的位置。在整个排序过程中,我们一共需要三个指针tail、cur、pre,cur用来遍历链表,tail用来确定已排好序的部分,pre用来找到cur要插入的位置。首先来看怎样寻找需要改变位置的结点。

找结点
struct ListNode *tail = head;
    struct ListNode *cur = head->next;
    while (cur != NULL) {
        if (tail->val <= cur->val) {
            tail = tail->next;
            cur = tail->next;
        }

这样,cur就指向了要改变位置的那个结点,而tail指向排好位置的最后一个结点。接下来来查找cur要插入的位置并改链。

找位置
struct ListNode *pre = shead;
            while (pre->next->val <= cur->val) {
                pre = pre->next;
            } //查找位置
            tail->next = cur->next;
            cur->next = pre->next;
            pre->next = cur; //改链
            cur = tail->next; //从下一个结点开始遍历

这里改链的操作如图:

完整的代码如下:

struct ListNode *insertionSortList(struct ListNode *head) {
    if (head == NULL) {
        return head;
    }
    struct ListNode *shead = malloc(sizeof(struct ListNode));
    shead->val = 0;
    shead->next = head; //设置虚拟头结点
    struct ListNode *tail = head;
    struct ListNode *cur = head->next;
    while (cur != NULL) {
        if (tail->val <= cur->val) {
            tail = tail->next;
        } else {
            struct ListNode *pre = shead;
            while (pre->next->val <= cur->val) {
                pre = pre->next;
            }
            tail->next = cur->next;
            cur->next = pre->next;
            pre->next = cur;
        }
        cur = tail->next;
    }
    return shead->next;
}

选择排序

选择排序是最符合人的直觉的一种排序方式,以从小到大排序为例,选择排序就是在所有数中选择最小的那一个放入已排序的表中,再在剩下的元素中选择最小的一个元素放入排好序的尾部,这样选择完最后一个元素时,就会得到一个从小到大的数组。

数组选择排序

首先选择排在第一个位置的元素,在整个数组中找到最小的那个元素,也就是-4,将-4与第一个元素7交换位置。

然后寻找第二个位置的元素,在除掉第一个元素的剩下的数组中找到最小的元素,也就是3,将3与5互换位置。

再找第三个位置的元素,剩下的元素为5、7,5为最小值,则不用交换。

最后得到一个从小到大排列的数组

链表选择排序

链表的选择排序思路与数组类似,以从小到大排序为例,要先确定选择元素插入的位置,再在剩下的表中找到最小值的结点,将该节点插入之前确定的位置中。因此我们就需要五个指针tail,cur,cur_pre,min,min_pre。tail同样用来确定已经排好序的部分,cur用来遍历,而min用来记录最小值,cur_pre和min_pre分别是cur和pre之前的那个结点,用来改链。

首先来看如何确定要选择元素插入的位置。

定位置
 for (tail = shead; tail != NULL && tail->next != NULL; tail = tail->next) {
        min = tail->next;
    }

由于要选择元素插入的位置就是从前到后的每一个位置,所以直接遍历即可,确定位置后,假设该位置元素为最小值,再与之后的元素比较,如果后续元素中的最小值大于该元素,则该元素就是真正的最小值,则不用交换位置,直接开始处理下一个位置;如果后续元素中最小值小于该元素,则将该元素与最小值交换。找元素的过程我们用cur来遍历tail之后的链表即可实现。具体的代码实现如下:

找结点
for (cur = tail->next, cur_pre = tail; cur != NULL; cur_pre = cur, cur = cur->next) {
            if (cur->val < min->val) {
                min = cur;
                min_pre = cur_pre; //找最小结点
            }
        }
        if (tail->next != min) {
            min_pre->next = min->next;
            min->next = tail->next;
            tail->next = min; //改链
        }

ps:这里的if语句是因为前文假设tail->next = min,如果最后min == tail->next,说明没有找到比tail->next更小的值,则该位置不用交换元素。

这里改链的操作如图:

完整的代码如下:

struct ListNode* insertionSortList(struct ListNode* head) {
    struct ListNode* shead = (struct ListNode*)malloc(sizeof(struct ListNode));
    shead->next = head; //设置虚拟头结点
    struct ListNode* tail, *cur_pre, *cur, *min, *min_pre;
    for (tail = shead; tail != NULL && tail->next != NULL; tail = tail->next) {
        min = tail->next;
        for (cur = tail->next, cur_pre = tail; cur != NULL; cur_pre = cur, cur = cur->next){
            if (cur->val < min->val) {
                min = cur;
                min_pre = cur_pre;
            }
        }
        if (tail->next != min) {
            min_pre->next = min->next;
            min->next = tail->next;
            tail->next = min;
        }
    }
    head = shead->next;
    free(shead);
    return head;
}

冒泡排序

冒泡排序是一种符合计算机逻辑的排序,以从小到大排序为例,每两个元素比较一次,并让这两个元素按照要求的顺序排列,这样一轮可以确定一个最大值,在n-1轮后,就可以得到一个有序的表。这样像泡泡一样从前往后走就叫冒泡排序

数组冒泡排序

第一轮冒泡,前两个元素比较,7>5,互换位置

然后7与3比较,互换位置

然后7与-4比较,互换位置

一轮冒泡完成后,确定了7在末尾,接下来开始第二轮冒泡,5大于3,互换位置

5大于-4,互换位置

第三轮冒泡,3大于-4,互换位置

这样就得到了一个从小到大排列的数组

链表冒泡排序

链表的冒泡排序同样也是两个结点两个结点比较,经过n-1轮冒泡后,就可以得到一个有序的表,而这里要实现计数n-1轮,就需要一个tail指针来记录已经排好序的部分,每次让指针遍历到tail就停止就好了,除此之外,我们还需要两个指针,cur用来遍历,pre是cur前一个结点,用来对cur改链。首先来看怎样实现统计轮数

记轮数
while (shead->next != NULL && shead->next->next != tail) {
        pre = shead;
        cur = shead->next;
        while (cur->next != tail) {
            pre = pre->next;
            cur = pre->next;
        } //cur移动到tail之前的那一个元素
        tail = cur; //tail向前进一位
    }

这样tail每次都向前进一位,也就实现了统计已排好序的结点和统计轮数的功能。

两两比较
while (cur->next != tail) {
            if (cur->val > cur->next->val) {
                pre->next = cur ->next;
                cur->next = pre->next->next;
                pre->next->next = cur;
            } //改链
            pre = pre->next;
            cur = pre->next;
        }

两两比较,如顺序不对则换位,这里的改链操作如下

完整的代码如下:

struct ListNode *insertionSortList(struct ListNode *head) {
     struct ListNode* shead = (struct ListNode*)malloc(sizeof(struct ListNode));
    shead->next = head; //设置虚拟头结点
    struct ListNode* tail = NULL, *cur, *pre;
    while (shead->next != NULL && shead->next->next != tail) {
        pre = shead;
        cur = shead->next;
        while (cur->next != tail) {
            if (cur->val > cur->next->val) {
                pre->next = cur ->next;
                cur->next = pre->next->next;
                pre->next->next = cur;
            }
            pre = pre->next;
            cur = pre->next;
        }
        tail = cur;
    }
    head = shead->next;
    free(shead);
    return head;
    return shead->next;
}

快速排序

快速排序是一种利用递归完成的排序,以升序为例,设定一个基准值,比基准值小的放在左边,比基准值大的放在右边。这样表就被分成了两个新的空间,在新的空间里又设定新的基准值重复上述操作,这样通过递归就可以得到一个标准的升序排列的表。

数组快速排序

先设定基准值为第二个元素3,将比3小的元素放在左边,大的元素放在右边

得到两个新的区间,设新区间的基准值分别是-4和7,-4小于0,位置不变。7大于5,5放到7的左边

这样就得到了两个有序区间,这两个有序区间合并就是一个有序的数组。

链表快速排序

链表快速排序的思路与数组一样,也是通过基准值不断分区间,直到有序。这里我们需要基准值privot,还需要两个指针,cur用来遍历区间内结点,pre是cur前一个结点,用来对cur改链。我们先来看一看如何分区间

分区间
void quick_sort(struct ListNode* head, struct ListNode* tail)
{
    if(head == NULL || head->next == tail || head->next->next == tail)
    {
        return ;
    }
    struct ListNode *privot = partition(head, tail);  
    quick_sort(head, privot);
    quick_sort(privot, tail);
}

我们用一个函数patition返回区间内的基准值来划分出更小的区间,并对该区间递归调用,区间不断缩小,直到区间内只剩一个元素。

结点分类
 struct ListNode* partition(struct ListNode* head, struct ListNode* tail) {
    struct ListNode *cur, *pre, *privot; 
    privot = head->next;
    for(pre = privot, cur = privot->next; cur && cur != tail; cur = pre->next)
    {
        if(privot->val > cur->val)
        {
            pre->next = cur->next;
            cur->next = head->next; 
            head->next = cur; //改链
        }
        else 
        {
            pre = pre->next;  
        }
    }
    return privot;  
}

对区间内结点分类,比基准值小的在一边,比基准值大的在另一边。设基准值为区间内第一个元素,那么只需比较之后元素中比基准值大的,则直接跳过,比基准值小的,就放到基准值前,也就是表头就好。这里的改链操作如下:

完整的代码如下:

 struct ListNode* partition(struct ListNode* head, struct ListNode* tail) {
    struct ListNode *cur, *pre, *privot; 
    privot = head->next;
    for(pre = privot, cur = privot->next; cur && cur != tail; cur = pre->next)
    {
        if(privot->val > cur->val)
        {
            pre->next = cur->next;
            cur->next = head->next;
            head->next = cur;
        }
        else 
        {
            pre = pre->next;  
        }
    }
    return privot;  
}

void quick_sort(struct ListNode* head, struct ListNode* tail)
{
    if(head == NULL || head->next == tail || head->next->next == tail)
    {
        return ;
    }
    struct ListNode *privot = partition(head, tail);  
    quick_sort(head, privot);
    quick_sort(privot, tail);
}

学习完了四种链表的排序方法,去做道题练习一下吧,下面给出了一道力扣上的链表排序,这道题没有时间复杂度的要求,可以将上述四种排序都练习一遍噢!

小小链表排序,怎么能难倒我!

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值