数据结构笔记-排序算法

记录一下数据结构的排序算法:相关代码放在这里保存:https://gitee.com/lx2035/data_structure

1、插入排序

插入排序的思想是将数据按照一定顺序一个个插入到有序的表中,最终得到的序列就是排好序的数据。插入排序根据插入方式的不同,分为直接插入排序,折半插入排序,2路插入排序和表插入排序这几种,下面逐一进行说明:

1、直接插入排序

直接插入排序就是在添加新的记录的时候,使用顺序查找的方式找到要插入的位置,然后将数据插入。

下面是将{3,1,7,5,2,4,9,6}进行升序排序的过程:

  • 第一个3直接插入
    image
  • 插入1,1小于3,放到3前面
    image
  • 插入7,7大于3,放3后面
    image
  • 插入5,5在3和7之间,所以插进去
    image

基本就是这么个流程,后面的部分省略

下面看代码实现:

void InsertSort(int a[], int n)
{
    for (int i = 1; i < n; i++) /*一个个插入的动作*/
    {
        if (a[i] < a[i - 1]) /*当前元素小于前一个元素*/
        {
            int j = i - 1;
            int x = a[i]; /*将当前元素存到临时变量x*/
            while (j > -1 && x < a[j]) /*从当前位置向前遍历,找到一个比临时变量还小的值,找不到就是第一个了,这个由j控制循环结尾*/
            {
                a[j + 1] = a[j]; /*找的过程将每次不符合的都往后移动*/
                j--;
            }
            a[j + 1] = x; /*找到j了,将当前元素插入*/
        }
        print(a, n, i);
    }
}

2、折半插入排序

上面说的直接插入排序,后面的元素插入的时候,采用顺序插入的方式来查找,但是实际上前面的表我们已经排好序了,所以可以节省一点查找量,因此就有折半查找这种思路:

下面直接看代码:
image

3、2路插入排序

2路插入排序是在折半插入排序的基础上对其进行改进,减少其在排序过程中移动记录的册数来提高效率。

实现上是另外设置一个和储存记录的数组大小相同的数组d,将无序表第一个加到d[0]的位置,之后从无序表的第二个记录开始,同0作比较,如果该值比d[0]大,就加到右侧,反之就是左侧。

还是用这个举例子,{3,1,7,5,2,4,9,6}

  • 一开始还是加入3到d[0]
    image
  • 插入1,1比3小,所以插入到左边
    image
  • 插入7,7比1大,所以插入到右边
    image
  • 将5插入,5比3大,插入右边,但是比7小,所以在中间
    image
  • 插入2,2很特殊,2比3小,应该插入左边,但是左边是1,所以只能让3整体都移动
    image
  • 插入4,和之前5类似,找个中间位置插入
    image
  • 插入9,一样的,找个中间位置插入
    image
  • 插入6,一样的,找个中间位置插入
    image

下面看代码实现:

void insert(int arr[], int temp[], int n)
{
    int i, first, final, k;
    first = final = 0; // 分别记录temp数组中最大值和最小值的位置
    temp[0] = arr[0]; /*将一个元素放到临时数组的第一个位置*/
    for (i = 1; i < n; i++)
    {
        if (arr[i] < temp[first]) // 待插入元素比最小的元素小
        {
            first = (first - 1 + n) % n; /*更新最小元素的位置*/
            temp[first] = arr[i]; /*将带插入元素放入最小元素的位置,这个就是放到末尾了,就是示意图里面的a【0】左边的*/
        }
        else if (arr[i] > temp[final]) // 待插入元素比最大元素大
        {
            final = (final + 1 + n) % n; /*更新最大元素的位置*/
            temp[final] = arr[i];/*将带插入元素放入最大元素的位置*/
        }
        else // 插入元素比最小大,比最大小
        {
            k = (final + 1 + n) % n; /*k是待插入元素应该插入的位置,从最大的开始找*/
            // 当插入值比当前值小时,需要移动当前值的位置
            while (temp[((k - 1) + n) % n] > arr[i]) /*没找到之前就一直前移*/
            {
                temp[(k + n) % n] = temp[(k - 1 + n) % n];
                k = (k - 1 + n) % n; /*找到k*/
            }
            // 插入该值
            temp[(k + n) % n] = arr[i];
            // 因为最大值的位置改变,所以需要实时更新final的位置
            final = (final + 1 + n) % n;
        }
    }
    // 将排序记录复制到原来的顺序表里
    for (k = 0; k < n; k++)
    {
        arr[k] = temp[(first + k) % n];
    }
}

4、表插入排序

前面几种插入排序算法,基本都是用数组的形式来存储数据,因此无法避免排序过程中产生数据移动的问题,如果要根本上解决只能改变数据的存储结构,改为使用链表存储。根据链表存储的特点,在存储的过程中,不需要移动记录的存储位置,只需要更改节点间指针的指向。

这个用链表的没怎么看懂,先不记录

5、缩小增量排序(希尔排序)

这个也是插入排序的一种,但是和前面的几种相比,在时间效率上有很大的改进,直接插入排序算法时,如果表中的记录只有个别的是无序的,多数保持有序,这种情况下算法的效率也会比较高;除此之外,如果需要排序的记录总量很少,该算法的效率同样会很高。希尔排序就是从这两点出发对算法进行改进得到的排序算法

这个算法的实现思路就是,先将整个记录表分割成若干个部分,分别进行直接插入排序,然后对整个记录表进行一次直接插入排序

具体实现如下,对这个表进行插入排序{49, 38, 65, 97, 76, 13, 27, 49, 55, 4}

  • 先对{49, 13}, {38, 27}, {65, 49}, {97, 55}, {76, 4} 分别进行直接插入排序,其实就是比大小
    image
  • 讲过第一次简单排序之后,表中的记录已经基本有序,在进行一次分割
    image
  • 最后再来一次完整的直接插入排序,这个过程其实完整插入排序要做的工作其实已经很少了,最后在来一下就是这样了
    image

下面看代码

typedef struct
{
    int key;
} SLNode;

typedef struct
{
    SLNode r[SIZE]; /*存记录的数组*/
    int length; /*记录数组中记录的数量*/
} SqList;

void ShellInsert(SqList *L, int dk) /*dk的增量*/
{
    for (int i = dk + 1; i <= L->length; i++) /*每隔dk做一下直接插入排序*/
    {
        if (L->r[i].key < L->r[i - dk].key)/*新调取的关键字的值,比子表中最后一个还小,就需要调换位置*/
        {
            int j;
            L->r[0] = L->r[i]; /*用0位置暂存需要换位置的值*/

            /*下面是直接插入排序*/
            for (j = i - dk; j > 0 && (L->r[0].key < L->r[j].key); j -= dk)
            {
                L->r[j + dk] = L->r[j];
            }
            L->r[j + dk] = L->r[0]; /*把值插入进去*/
        }
    }
}

void ShellSort(SqList *L, int dlta[], int t) /*对多个子表进行插入排序,插入排序的增量值由dlta控制*/
{
    for (int k = 0; k < t; k++)
    {
        ShellInsert(L, dlta[k]);
    }
}

2、冒泡排序算法

这个应该算是很经典的排序算法了吧,很多学c语言的都是会在正式的课程中学到,这里简单说一下实现的思路

冒泡排序对应一个换钻石的问题,比如一个人做电梯,每层楼都需要停一下,每层楼都有一颗钻石,这个人想要拿到最大的钻石,但是一次只能拿一颗,因此他就每次用自己手里的钻石和这层楼的钻石做比较,比他手里的大就换一下,这就是冒泡排序的思路了,冒泡排序每次每一轮冒泡选出一个最大或者最小值,这样到最后一轮的时候就是有序的了。

下面看代码实现:
image

3、快速排序算法

需要说明,c语言自带的函数库中就有快速排序,qsort函数,在<stdlib.h>这个头文件中,快速排序的思想是:

通过一次排序将整个无序表分成相互独立的两部分,其中一部分中的数据都比另一部分中包含的数据的值小,然后继续沿用此方法分别对两部分进行同样的操作,直到每一个小部分不可再分,所得到的整个序列就成为了有序序列。

下面使用对无序表{49, 38, 65, 97, 76, 13, 27, 49}进行快速排序作为示例:

  • 首先从表中选取一个记录的关键字作为分割点(称为“枢轴” 或者支点,一般选择第一个关键字),例如选取 49;
  • 将表格中大于 49 个放置于 49 的右侧,小于 49 的放置于 49 的左侧,假设完成后的无序表为: {27, 38, 13, 49, 65, 97, 76, 49};
  • 以 49 为支点,将整个无序表分割成了两个部分,分别为{27, 38, 13}和{65, 97, 76, 49},继续采用此种方法分别对两个子表进行排序;
  • 前部分子表以 27 为支点,排序后的子表为{13, 27, 38},此部分已经有序;后部分子表以 65 为支点,排序后的子表为{49, 65, 97,76};
  • 此时前半部分子表中的数据已完成排序;后部分子表继续以 65 为支点,将其分割为{49}和{97, 76},前者不需排序,后者排序后的结果为{76, 97};
  • 通过以上几步的排序,最后由子表{13, 27, 38}、 {49}、 {49}、 {65}、 {76, 97}构成有序表: {13, 27, 38, 49, 49, 65, 76, 97};

这里也贴代码吧,具体的还没怎么看明白


#include "stdio.h"
#include "stdlib.h"

#define MAX 9

typedef struct
{
    int key;
} SqNote;

typedef struct
{
    SqNote r[MAX];
    int length;
} SqList;

int Partition(SqList *L, int low, int high)
{
    L->r[0] = L->r[low];
    int pivotkey = L->r[low].key;

    while (low < high)
    {
        while (low < high && L->r[high].key >= pivotkey)
        {
            high--;
        }
        L->r[low] = L->r[high];
        while (low < high && L->r[low].key <= pivotkey)
        {
            low++;
        }
        L->r[high] = L->r[low];
    }
    L->r[low] = L->r[0];
    return low;
}

void QSort(SqList *L, int low, int high)
{
    if (low < high)
    {
        int pivotloc = Partition(L, low, high);
        QSort(L, low, pivotloc - 1);
        QSort(L, pivotloc + 1, high);
    }
}

void QuickSort(SqList *L)
{
    QSort(L, 1, L->length);
}

int main()
{
    SqList *L = (SqList *)malloc(sizeof(SqList));
    L->length = 8;
    L->r[1].key = 49;
    L->r[2].key = 38;
    L->r[3].key = 65;
    L->r[4].key = 97;
    L->r[5].key = 76;
    L->r[6].key = 13;
    L->r[7].key = 27;
    L->r[8].key = 49;
    QuickSort(L);
    for (int i = 1; i <= L->length; i++)
    {
        printf("%d ", L->r[i].key);
    }
    printf("\n");
    exit(0);
}

4、简单选择排序

这个算法的思想为:对于具有 n 个记录的无序表遍历 n-1 次,第 i 次从无序表中第 i 个记录开始,找出后序关键字中最小的记录,然后放置在第 i 的位置上。

简单来说就是第一次遍历找到一个最小的,然后和第一个位置的元素换位置,之后从第二个位置开始继续找最小的,然后和第二个元素换位置(和冒泡排序有点类似)

下面看实现代码
image

posted @   LX2020  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示