【算法分析与设计】第二章 排序

1.1、选择、冒泡、插入排序

a、选择排序
  • 算法描述
    1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
    2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
    3.重复第二步,直到所有元素均排序完毕。
  • 时间复杂度: O(N^2),空间复杂度: O(1)
  • 代码
template<typename T> //整數或浮點數皆可使用,若要使用物件(class)時必須設定大於(>)的運算子功能
void selection_sort(std::vector<T>& arr) {
        for (int i = 0; i < arr.size() - 1; i++) {
                int min = i;
                for (int j = i + 1; j < arr.size(); j++)
                        if (arr[j] < arr[min])
                                min = j;
                std::swap(arr[i], arr[min]);
        }
}
b、冒泡排序:每一趟遍历,将一个最大的数移到序列末尾。
  • 算法描述
    1.比较相邻的元素,如果前一个比后一个大,交换之。
    2.第一趟排序第1个和第2个一对,比较与交换,随后第2个和第3个一对比较交换,这样直到倒数第2个和最后1个,将最大的数移动到最后一位。
    3.第二趟将第二大的数移动至倒数第二位

    因此需要n-1趟
    在这里插入图片描述

  • 时间复杂度: O(N^2),空间复杂度: O(1)

  • 代码

#include <stdio.h>
#define ARR_LEN 255 /*数组长度上限*/
#define elemType int /*元素类型*/
 
void bubbleSort (elemType arr[], int len) {
    elemType temp;
    int i, j;
    for (i=0; i<len-1; i++) /* 外循环为排序趟数,len个数进行len-1趟 */
        for (j=0; j<len-1-i; j++) { /* 内循环为每趟比较的次数,第i趟比较len-i次 */
            if (arr[j] > arr[j+1]) { /* 相邻元素比较,若逆序则交换(升序为左大于右,降序反之) */
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
}
c、插入排序
  • 算法描述:
    1、将待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列;
    2、取出下一个元素,在已经排序的元素序列中从后向前扫描;
    3、如果该元素(已排序)大于新元素,将该元素移到下一位置;
    4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
    5、将新元素插入到该位置后;
    6、重复步骤2~5。

  • 时间复杂度: O(N^2),空间复杂度: O(1)

  • 代码

/* 插入排序*/
    int num[5] = {3, 7, 1, 8, 5};
    int pos, cur;
    int i;
    int length = sizeof(num)/sizeof(num[0]);

    for (i = 1; i < length; i++)	//从第二个元素开始
    {
        pos = i -1 ;    //有序序列的最后一个元素位置
        cur = num[i];    //保存待排序元素的值
        while ( pos >= 0 && num[pos] > cur)
        {
            num[pos + 1] = num[pos];
            pos--;
        }
        num[pos + 1] = cur;    //将待排序元素插入数组中
    }

1.2、快速排序

  • 算法描述
    快速排序(Quick Sort)使用分治法策略。
    它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序流程:
(1) 从数列中挑出一个基准值。
(2) 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边);在这个分区退出之后,该基准就处于数列的中间位置。
(3) 递归地把"基准值前面的子数列"和"基准值后面的子数列"进行排序。
在这里插入图片描述

  • 时间复杂度:O(N*lgN)

  • 代码

 /* 快速排序
 - 参数说明:
 -     a -- 待排序的数组
 -     l -- 数组的左边界(例如,从起始位置开始排序,则l=0)
 -     r -- 数组的右边界(例如,排序截至到数组末尾,则r=a.length-1)
 */
void quick_sort(int a[], int l, int r)
{
    if (l < r)
    {
        int i,j,x;
        i = l;
        j = r;
        x = a[i];
        while (i < j)
        {
            while(i < j && a[j] > x)
                j--; // 从右向左找第一个小于x的数
            if(i < j)
                a[i++] = a[j];
            while(i < j && a[i] < x)
                i++; // 从左向右找第一个大于x的数
            if(i < j)
                a[j--] = a[i];
        }
        a[i] = x;
        quick_sort(a, l, i-1); /* 递归调用 */
        quick_sort(a, i+1, r); /* 递归调用 */
    }
}

1.3、堆排序

a、堆

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
在这里插入图片描述
同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子在这里插入图片描述
该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

b、堆排序基本思想及步骤

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
1.假设给定无序序列结构如下在这里插入图片描述
2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整在这里插入图片描述
3.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。在这里插入图片描述
这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。在这里插入图片描述
此时,我们就将一个无需序列构造成了一个大顶堆。
步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
a.将堆顶元素9和末尾元素4进行交换在这里插入图片描述
b.重新调整结构,使其继续满足堆定义在这里插入图片描述
c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.在这里插入图片描述
后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序在这里插入图片描述
再简单总结下堆排序的基本思路:
  a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
  b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
  c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

c、代码实现

#include <iostream>
#include <algorithm>
using namespace std;
 
void max_heapify(int arr[], int start, int end) 
{
    //建立父节点指标和子节点指标
    int dad = start;
    int son = dad * 2 + 1;
    while (son <= end)  //若子节点指标在范围内才做比较
    {    
        if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的
            son++;
        if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数
            return;
        else  //否则交换父子内容再继续子节点和孙节点比较
        {
            swap(arr[dad], arr[son]);
            dad = son;
            son = dad * 2 + 1;
        }
    }
}
 
void heap_sort(int arr[], int len) 
{
    //初始化,i从最後一个父节点开始调整
    for (int i = len / 2 - 1; i >= 0; i--)
        max_heapify(arr, i, len - 1);
    //先将第一个元素和已经排好的元素前一位做交换,再从新调整(刚调整的元素之前的元素),直到排序完毕
    for (int i = len - 1; i > 0; i--) 
    {
        swap(arr[0], arr[i]);
        max_heapify(arr, 0, i - 1);
    }
}
 
void main() 
{
    int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    heap_sort(arr, len);
    for (int i = 0; i < len; i++)
        cout << arr[i] << ' ';
    cout << endl;
    system("pause");
}

d、时间复杂度O(nlogn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值