(九)C语言中阶--排序算法

一、交叉类排序

1、冒泡排序

基本思想
1、从后往前(或从前往后)两两比较相邻元素的值,如果前一个数大于后一个数,则交换位置,
2、直到比较完,把最小的值交换到第一个位置
3、进第二次冒泡排序,前一次确认的最小元素不用参与比较,
最大执行(n-1)次冒泡排序就能把所有的元素拍好
冒泡过程初始第一次第二次第三次
数组开头8444
7855
5787
数组结尾4578
代码实现

步骤

1、随机生成10个元素
2、打印随机生成后的元素顺序
3、执行冒泡排序
3.1、需要两层冒泡排序,外层控制有序数的数数目(已经排好序的数字的个数),内层控制比较和交换
(优先写内部循环,再写外部循环)
4、打印排序后的元素顺序
// 交换函数
void swap(int &a, int &b){
    int temp=a;
    a=b;
    b=temp;
}
// 冒泡排序
void BubbleSort(Elemtype *elem, int len){
    int i,j;
    // 哨兵
    bool flog= false;
    // i最多访问到len-1, 如果要是到第len次时,内层函数进不去,没有意义
    for (i = 0; i < len-1 ; i++) {
        for (j = len-1; j > i; j--) {
            if (elem[j-1]>elem[j]){
                swap(elem[j-1],elem[j]);
                flog= true;
            }
        }
        // 如果在一趟比较中没有发生任何交换,说明有序,可以提前结束排序
        if (flog== false){
            return;
        }
    }
}

2、快速排序

递归实现

核心原理

核心就是分治思想

例如:28  7 98 28 60 83 34 59 69 14
1、取第一个元素 28,
2、把比28小的元素移到28的左侧,把比28的元素移到28的右侧
即形成7 14 28 [28] 98 60 83 34 59 69
3、把左侧和右侧再次使用快排进行排序,左侧去7,右侧去98
即形成[7] 14 28 [28] 60 83 34 59 69 [98],循环进行直至排序完成
代码实现

步骤 挖坑法

(1)  初始:64 7 98 28 60 83 34 59 69 14
(2)  用临时变量 pivot=64,high = 14 < 64,用high的值把low覆盖(此时low不动,移动high)
第一次:  14 7 98 28 60 83 34 59 69 14
(3) low++ 当找到 low=98 > 64,用low的值把high覆盖  (此时high不动,移动low)
第二次:  14 7 98 28 60 83 34 59 69 98
---循环往复---
(4) 当low和high相等时,循环结束,此时low为NULL,可以把64覆盖回来
==14 7 59 28 60 34 [64] 83 69 98==
(5) 返回
// 分割函数
int partition(Elemtype *elem, int low, int high){
    //最左边的作为分割值并存储;
    Elemtype piovt=elem[low];
    while (low<high){
        while (low<high&&elem[high]>=piovt) //从后往前遍历,找到比分割值小的
            high--;
        elem[low]=elem[high]; // 把比分割值小的元素放到low
        while (low<high&&elem[low]<=piovt) //从前往后遍历,找到比分割值大的
            low++;
        elem[high]=elem[low]; // 把比分割值大的元素放到high
    }
    elem[low]=piovt;
    // 返回下标
    return low;
}
// 快速排序
void QuickSort(Elemtype *elem, int low, int high){
    if (low<high){
        // low取最左侧元素,high取最右侧元素
        // pivot 用来存分隔值位置
        int pivot_pos=partition(elem, low, high);
        // 前一半递归拍好
        QuickSort(elem, low, pivot_pos-1);
        // 后一半递归拍好
        QuickSort(elem, pivot_pos+1, high);
    }
}

二、插入类排序

插入排序分为:直接插入排序,折半插入排序,希尔插入排序

直接插入排序

原理
如果序列只有一个数,那么该序列自然有序,把后面的数当做依次要插入的序列。
通过外出循环控制要插入的数。
用一个变量存要插入的值,比较arr[0]和arr[1],如果arr[0]>arr[i]发生移动,反之不移动。
代码实现
// 插入排序
void InsertSort(Elemtype *elem, int n){
    int i,j,insertVal;
    for (i = 1; i < n; i++) {  //外出循环控制要插入的数
        insertVal=elem[i]; //用一个变量存要插入的值
        for (j = i-1; j>=0 && elem[j]>insertVal; j--) {
            elem[j+1]=elem[j];
        }
        elem[j+1]=insertVal; //把要插入的元素放入对应的位置
    }
}

三、选择类排序

1、简单选择排序

原理解析

假设排序表L[1...n],第i趟排序即从L[i,n]选出最小的元素与L[i]交互,每一趟排序确定一个元素的最终位置,经过n-1次后完成排序
步骤

1. 假设第一个元素最小,把第一个元素的下标赋给min
2. 内层循环比较 1- 9 号元素,谁更小就把它的下标赋值给min,
3. 一轮比较结束后,把第一个元素和最小元素交换
最初3 87 2 93 78 56 61 38 12 40
第一轮比较后标记3 87 2 93 78 56 61 38 12 40
第一次交互2 87 3 93 78 56 61 38 12 40
第二轮比较后标记2 87 3 93 78 56 61 38 12 40
第二次交互2 3 87 93 78 56 61 38 12 40
代码实现
// 交换函数
void swap(int &a, int &b){
    int temp=a;
    a=b;
    b=temp;
}
// 选择排序
void selectionSort(Elemtype *elem, int n){
    int min;
    for (int i = 0; i < n; i++) {
        min=i;
        // j从第一个元素开始
        for (int j = i+1; j < n; j++) {
            if (elem[j]<elem[min]){
                min=j; // 标记最小元素
            }
        }
        // 循环比较了一次后, 交换位置
        if (min!=i){
            swap(elem[i],elem[min]);
        }
    }
}

2、堆排序

原理解析

一种特殊的数状结构
特点

1.给定堆中任意结点P和C,若P是C的父节点,则P的值小于等于(或大于等于)C的值
2.若父节点的值恒小于等于子节点,称之为最小堆,反之称之为最大堆
3.堆中最顶结点称之为根节点
4.根节点没有父节点

假设有 3 87 2 93 78 56 61 38 12 40 十个元素,使用层次建树法建立成完全二叉树_相关内容(六)数据结构–二叉树_
能将二叉树中每个元素对应到数组下标的数据结构叫做
最后一个父亲结点下标 N/2-1(完全二叉树的特性)

代码实现

步骤(包含两个大步骤)

1.实现大根堆
1.1 从叶子结点开始比较,
如果父节点只有一个子节点,并且子节点的值比父节点的值大,子节点和父节点交换位置,反之不用交换位置;
如果父节点有两个子节点,先选出较大的子节点与父节点进行比较,子节点的值比父节点的值大,子节点和父节点交换位置,直至循环到arr[0]
再由arr[1]作为根节点和子节点进行比较,循环往复直至实现大根堆
2.实现排序
先把顶部元素与最后一个元素进行交换,
再次通过大根堆将处理最后元素的最大元素移动到最顶部
再次把顶部元素与最后一个今夕交换,
再次通过大根堆调整元素,循环往复直至排序结束

需要公式:
最后一个父亲结点=n/2-1;
左子树下标=2*dad+1;
右子树下标=左子树下标+1;

// 把某个子树调整为大根堆
void AdjustDown(Elemtype *elem, int k, int len){
    int dad=k; // 父亲下标
    int son=2*dad+1; // 左孩子下标
    while (son<len){
        // 最后的右孩子下标小于总长度
        if (son+1<len && elem[son]<elem[son+1]){
            son++; // 那右孩子和父亲比
        }
        // 拿孩子和父亲比较
        if (elem[son]>elem[dad]){
            swap(elem[son],elem[dad]);
            // 当父子结点交换后,如果交换成子节点的值同时也是父节点
            // son重新作为dad去验证;
            dad=son;
            son=2*dad+1;
        } else {
            break;
        }
    }
}
// 堆排序
void heap_Sort(Elemtype *elem, int len){
    // 实现大根堆
    for (int i = len/2-1; i >=0 ; i--) {
        AdjustDown(elem, i, len);
    }
    // 交换元素排序
     swap(elem[0], elem[len-1]);
    for (int i = len-1; i>1; i--) { // i代表的是剩余无序数的数组的长度
        AdjustDown(elem ,0, i); // 调整剩余元素变成大根堆
        swap(elem[0],elem[i-1]);
    }
}

四、归并排序

原理

两两归并,将每两个元素归为一组,在组内排序,再把两个有序组合并成一个有序小组,不断进行最终合并成一个有序组。

49    38    65    97    76    13    27   初始化
------------------------------------------------------------
[38    49] [65    97] [13    76]    27   第一次归并
------------------------------------------------------------
[38    49    65    97] [13    76    27]  第二次归并
------------------------------------------------------------
[13    27    38    49    65    76    97]  第三次归并
代码实现

递归实现

// 合并两个有序数组
void Merge(Elemtype *elem, int low, int mid, int high){
    static Elemtype B[N];  //static的目的是无论递归调用了多少次,都只有一个B[N];
    int i, j, k;
    // 复制数组
    for (i = low; i <=high ; i++) {
        B[i]=elem[i];
    }
    k=low;
    // 合并两个有序数组
    for (i = low, j=mid+1; i <= mid && j<=high;) {
        if (B[i]<B[j]){
            elem[k]=B[i];
            k++;
            i++;
        } else {
            elem[k]=B[j];
            k++;
            j++;
        }
    }
    // 把某一个循环数组剩余的元素放进来
    while (i<=mid){
        elem[k]=B[i];
        i++;
        k++;
    }
    while (j<=high){
        elem[k]=B[j];
        j++;
        k++;
    }
}
// 归并排序
void MergeSort(Elemtype *elem, int low, int high){
    if (low<high){
        int mid=(low+high)/2;
        MergeSort(elem,low,mid); // 排序好前一半
        MergeSort(elem,mid+1,high); // 排序好后一半
        Merge(elem,low,mid, high);  // 合并数组
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Y_努力_strive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值