0
点赞
收藏
分享

微信扫一扫

十大排序算法总结

追风骚年 2022-04-02 阅读 150

十大排序算法总结

image

文章目录

名词解释

  • 稳定 :如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
  • 不稳定 :如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
  • 内排序 :所有排序操作都在内存中完成;
  • 外排序 :由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;

image

  • 图片名词解释:
    • n: 数据规模
    • k: “桶”的个数
    • In-place: 占用常数内存,不占用额外内存
    • Out-place: 占用额外内存

1、冒泡排序

1.1 算法步骤

  • 步骤1: 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • 步骤2: 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • 步骤3: 针对所有的元素重复以上的步骤,除了最后一个;
  • 步骤4: 重复步骤1~3,直到排序完成。

1.2 动图演示

1.3 代码实现

#include <bits/stdc++.h>
using namespace std;
int main(){
    int arr[10] = {2,8,4,9,0,12,16,1,7,6};
    // 第一层循环是代表要循环比较的趟数
    for (int i = 0; i < 10-1; ++i) {
        //  每一层循环要比较的次数
        for (int j = 0; j < 10-1-i; ++j) {
            // 大的数据往上浮动
            if (arr[j+1]<arr[j])
                swap(arr[j+1],arr[j]);
        }
    }
    for (int k = 0; k < 10; ++k) {
        cout<<arr[k]<<" ";
    }
    return 0;
}

image-20220401175450697

1.4 时间复杂度

  • 最佳情况:T(n) = O(n)
  • 最差情况:T(n) = O(n2)
  • 平均情况:T(n) = O(n2)

2、选择排序

2.1 算法步骤

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  3. 重复第二步,直到所有元素均排序完毕

2.2 动图演示

2.3 代码实现

#include <bits/stdc++.h>
using namespace std;
int main(){
    int arr[10] = {2,8,4,9,0,12,16,1,7,6};
    // 第一层循环是代表要循环比较的趟数
    for (int i = 0; i < 10-1; ++i) {
        //  记录最小的下标
        int index = i;
        for (int j = i+1; j < 10; ++j) {
            //  更新最小的下标
            if (arr[index]>arr[j])
                index=j;
        }
        swap(arr[i],arr[index]);
    }
    for (int k = 0; k < 10; ++k) {
        cout<<arr[k]<<" ";
    }
    return 0;
}

2.4 时间复杂度

  • 最佳情况:T(n) = O(n2)
  • 最差情况:T(n) = O(n2)
  • 平均情况:T(n) = O(n2)

3、插入排序

3.1 算法步骤

  1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

3.2 动图演示

3.3 代码实现

#include <bits/stdc++.h>
using namespace std;
int main(){
    int arr[15] = {2,8,4,9,0,12,16,1,7,6,6,8,45,-7,-3};
    int len = 15;
    // 将1~len的数组看做是待排序的数组序列
    for (int i = 1; i < len; ++i) {
        int j=i-1;
        // 将i插入到合适的位置
        while (j>=0&&arr[j+1]<arr[j]){
            swap(arr[j+1],arr[j]);
            j--;
        }
    }
    for (int k = 0; k < len; ++k) {
        cout<<arr[k]<<" ";
    }
    return 0;
}

image-20220401193040325

3.4 时间复杂度

  • 最佳情况:T(n) = O(n)
  • 最坏情况:T(n) = O(n2)
  • 平均情况:T(n) = O(n2)

4、希尔排序

4.1 算法步骤

  1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
  2. 按增量序列个数 k,对序列进行 k 趟排序;
  3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

4.2 动图演示

image

4.3 代码演示

#include <bits/stdc++.h>
using namespace std;
int main(){
    int arr[15] = {2,8,4,9,0,12,16,1,7,6,6,8,45,-7,-3};
    int len = 15;
    int gap = len/2;
    // 初始增量gap=len/2,按增量序列个数k,对序列进行 k 趟排序;
    while (gap>0){
        // 对每一个序列进行插入排序
        for (int i = gap; i < len; ++i) {
            int j = i-gap;
            while (j>=0&&arr[j]>arr[j+gap]){
                swap(arr[j],arr[j+gap]);
                j-=gap;
            }
        }
        gap/=2;
    }
    for (int k = 0; k < len; ++k) {
        cout<<arr[k]<<" ";
    }
    return 0;
}

image-20220401201127854

4.4 时间复杂度

  • 最佳情况:T(n) = O(nlog2 n)
  • 最坏情况:T(n) = O(nlog2 n)
  • 平均情况:T(n) =O(nlog2n)

5、归并排序

5.1 算法步骤

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  4. 重复步骤 3 直到某一指针达到序列尾;
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

5.2 动图演示

5.3 代码实现

#include <bits/stdc++.h>
using namespace std;
int tmp[15];
int arr[15]={2,8,4,9,0,12,16,1,7,6,6,8,45,-7,-3};
void merge_sort(int a[], int l, int r)
{
    if (l >= r)
        return;
    int mid = (l + r) / 2;
    merge_sort(a, l, mid);
    merge_sort(a, mid + 1, r);
    int k = 0, i = l,j = mid + 1;
    while (i <= mid && j <= r)
    {
        if (a[i] < a[j])
            tmp[k++] = a[i++];
        else
            tmp[k++] = a[j++];
    }
    while (i <= mid)
        tmp[k++] = a[i++];
    while (j <= r)
        tmp[k++] = a[j++];
    for (i = l, j = 0; i <= r; ++i, ++j)
        a[i] = tmp[j];
}
int main(){
    int len = 15;
    merge_sort(arr,0,len-1);
    for (int k = 0; k < len; ++k) {
        cout<<arr[k]<<" ";
    }
    return 0;
}

image-20220401202315879

5.4 时间复杂度

  • 最佳情况:T(n) = O(n)
  • 最差情况:T(n) = O(nlogn)
  • 平均情况:T(n) = O(nlogn)

6、快速排序

6.1 算法步骤

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

6.2 动图演示

6.3 代码演示

#include <bits/stdc++.h>
using namespace std;
int arr[15]={2,8,4,9,0,12,16,1,7,6,6,8,45,-7,-3};
void quick_sort(int a[], int l, int r)
{
    if (l >= r)
        return;
    int flag = a[(l+r)/2], i = l - 1, j = r + 1;
    while (i < j)
    {
        do
            i++;
        while (a[i] < flag);
        do
            j--;
        while (a[j] > flag);
        if (i < j)
            swap(a[i], a[j]);
        else
            break;
    }
    quick_sort(a, l, j);
    quick_sort(a, j + 1, r);
}
int main(){
    int len = 15;
    quick_sort(arr,0,len-1);
    for (int k = 0; k < len; ++k) {
        cout<<arr[k]<<" ";
    }
    return 0;
}

image-20220401212221920

6.4 时间复杂度

  • 最佳情况:T(n) = O(nlogn)
  • 最差情况:T(n) = O(n2)
  • 平均情况:T(n) = O(nlogn)

7、堆排序

7.1 算法步骤

  1. 创建一个堆 H[0……n-1];
  2. 把堆首(最大值)和堆尾互换;
  3. 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
  4. 重复步骤 2,直到堆的尺寸为 1。

7.2 动图演示

7.3 代码实现

#include <bits/stdc++.h>

using namespace std;
int a[15] = {2, 8, 4, 9, 0, 12, 16, 1, 7, 6, 6, 8, 45, -7, -3};
int alen = 15;

// 构造大根堆,通过新插入的数上升
void heapInsert(int arr[], int len) {
    for (int i = 0; i < len; ++i) {
        //  当前插入的索引
        int currentIndex = i;
        //  父结点
        int fatherIndex = (currentIndex - 1) / 2;
        //  如果当前插入的值大于其父亲结点的值,则交换值,并且将索引指向父节点
        //  然后继续和上面的父节点值比较,直到不大于父亲结点,则退出循环
        while (arr[currentIndex] > arr[fatherIndex]) {
            //交换当前结点与父结点的值
            swap(arr[fatherIndex], arr[currentIndex]);
            //将当前索引指向父索引
            currentIndex = fatherIndex;
            //将当前索引指向父索引
            fatherIndex = (currentIndex - 1) / 2;
        }
    }
}
//将剩余的数构造成大根堆(通过顶端的数下降)
void heapify(int arr[], int index, int size) {
    int left = 2 * index + 1;
    int right = 2 * index + 2;
    while (left < size) {
        //判断孩子中较大的值的索引(要确保右孩子在size范围之内)
        int largestIndex = arr[left] < arr[right] && right < size ? right : left;
        //判断孩子中较大的值的索引(要确保右孩子在size范围之内)
        if (arr[index] > arr[largestIndex]) {
            largestIndex = index;
        }
        //如果父结点索引是最大值的索引,那已经是大根堆了,则退出循环
        if (index == largestIndex)
            break;
        //父结点不是最大值,与孩子中较大的值交换
        swap(arr[largestIndex], arr[index]);
        //父结点不是最大值,与孩子中较大的值交换
        index = largestIndex;
        //重新计算交换之后的孩子的索引
        left = 2 * index + 1;
        right = 2 * index + 2;
    }
}

// 堆排序
void heapSort(int arr[], int len) {
    //构造大根堆
    heapInsert(arr, len);
    while (len > 1) {
        //构造大根堆
        swap(arr[0], arr[len - 1]);
        len--;
        //构造大根堆
        heapify(arr, 0, len);
    }
}

int main() {
    heapSort(a, alen);
    for (int i = 0; i < alen; ++i) {
        cout << a[i] << " ";
    }
    return 0;
}

image-20220402081949233

7.4 时间复杂度

  • 最佳情况:T(n) = O(nlogn)
  • 最差情况:T(n) = O(nlogn)
  • 平均情况:T(n) = O(nlogn)

8、计数排序

8.1 算法步骤

  • (1)找出待排序的数组中最大和最小的元素
  • (2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
  • (3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  • (4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去

8.2 动图演示

8.3 代码实现

#include <bits/stdc++.h>

using namespace std;
int a[15] = {2, 8, 4, 9, 0, 12, 16, 1, 7, 6, 6, 8, 45, -7, -3};
int alen = 15;

void CountingSort(int arr[],int len){
    int bias,cmin=arr[0],cmax=arr[0];
    //  找出整个数组里面最大值和最小值
    for (int i = 0; i < len; ++i) {
        if(arr[i]>cmax)
            cmax=arr[i];
        if(arr[i]<cmin)
            cmin=arr[i];
    }
    //  记录偏移量
    bias = -cmin;
    //  创建bucket数组,记录每个arr位置元素出现的次数
    int bucket[cmax-cmin+1];
    memset(bucket,0,sizeof(bucket));
    for (int j = 0; j < len; ++j) {
        bucket[arr[j]+bias]++;
    }
    int index=0,i=0;
    //  从bucket里面还原出arr内容
    while (index<len){
        if (bucket[i]!=0){
            arr[index]=i-bias;
            bucket[i]--;
            index++;
        } else
            i++;
    }
}

int main() {
    CountingSort(a,alen);
    for (int i = 0; i < alen; ++i) {
        cout << a[i] << " ";
    }
    return 0;
}

8.4 时间复杂度

  • 最佳情况:T(n) = O(n+k)
  • 最差情况:T(n) = O(n+k)
  • 平均情况:T(n) = O(n+k)

9、桶排序

1. 什么时候最快

当输入的数据可以均匀的分配到每一个桶中。

2. 什么时候最慢

当输入的数据被分配到了同一个桶中。

3. 示意图

元素分布在桶中:

img

然后,元素在每个桶中排序:

img

9.3 代码实现

import java.util.ArrayList;

public class BucketSort {
    //在链表中添加元素的同时需要进行元素的排序
    public static void sort(ArrayList<Integer> list, int i) {
        if(list==null)
            list.add(i);
            //这里采用的排序方式为插入排序
        else {
            int flag=list.size()-1;
            while(flag>=0&&list.get(flag)>i) {
                if(flag+1>=list.size())
                    list.add(list.get(flag));
                else
                    list.set(flag+1, list.get(flag));
                flag--;
            }
            if(flag != (list.size()-1))
                //注意这里是flag+1,自己可以尝试将这里换成flag看看,会出现数组越界的情况
                list.set(flag+1, i);
            else
                list.add(i);
        }
    }
    public static void Bucketsort(int []num,int sum) {
        //遍历得到数组中的最大值与最小值
        int min=Integer.MAX_VALUE;
        int max=Integer.MIN_VALUE;
        for(int i=0;i<num.length;i++) {
            min = min <= num[i] ? min: num[i];
            max = max >= num[i] ? max: num[i];
        }
        //求出每个桶的长度,这里必须使用Double
        double size=(double)(max-min+1)/sum;
        ArrayList<Integer>list[]=new ArrayList[sum];
        for(int i=0;i<sum;i++) {
            list[i]=new ArrayList<Integer>();
        }
        //将每个元素放入对应的桶之中同时进行桶内元素的排序
        for(int i=0;i<num.length;i++) {
            System.out.println("元素:"+String.format("%-2s", num[i])+", 被分配到"+(int)Math.floor((num[i]-min)/size)+"号桶");
            sort(list[(int)Math.floor((num[i]-min)/size)], num[i]);
        }
        System.out.println();
        for(int i=0;i<sum;i++) {
            System.out.println(String.format("%-1s", i)+"号桶内排序:"+list[i]);
        }
        System.out.println();
        //顺序遍历各个桶,得出我们 已经排序号的序列
        for(int i=0;i<list.length;i++) {
            if(list[i]!=null){
                for(int j=0;j<list[i].size();j++) {
                    System.out.print(list[i].get(j)+" ");
                }
            }
        }
        System.out.println();
    }
    public static void main(String[] args) {

        int []num ={7,4,3,2,6,5,-2,-1,4,5,6,1,7,3};
        //这里桶的数量可以你自己定义,这里我就定义成了3
        Bucketsort(num, 5);

    }

}

image-20220402102055704

9.4 时间复杂度

  • 最佳情况:T(n) = O(n+k)
  • 最差情况:T(n) = O(n+k)
  • 平均情况:T(n) = O(n2)

10、基数排序

10.1 算法描述

  • 步骤1:取得数组中的最大数,并取得位数;
  • 步骤2:arr为原始数组,从最低位开始取每个位组成radix数组;
  • 步骤3:对radix进行计数排序(利用计数排序适用于小范围数的特点);

10.2 动图演示

10.3 代码实现

 /**
     * 基数排序
     * @param array
     * @return
     */
    public static int[] RadixSort(int[] array) {
        if (array == null || array.length < 2)
            return array;
        // 1.先算出最大数的位数;
        int max = array[0];
        for (int i = 1; i < array.length; i++) {
            max = Math.max(max, array[i]);
        }
        int maxDigit = 0;
        while (max != 0) {
            max /= 10;
            maxDigit++;
        }
        int mod = 10, div = 1;
        ArrayList<ArrayList<Integer>> bucketList = new ArrayList<ArrayList<Integer>>();
        for (int i = 0; i < 10; i++)
            bucketList.add(new ArrayList<Integer>());
        for (int i = 0; i < maxDigit; i++, mod *= 10, div *= 10) {
            for (int j = 0; j < array.length; j++) {
                int num = (array[j] % mod) / div;
                bucketList.get(num).add(array[j]);
            }
            int index = 0;
            for (int j = 0; j < bucketList.size(); j++) {
                for (int k = 0; k < bucketList.get(j).size(); k++)
                    array[index++] = bucketList.get(j).get(k);
                bucketList.get(j).clear();
            }
        }
        return array;
    }

10.4 时间复杂度

  1. 最佳情况:T(n) = O(n * k

  2. 最差情况:T(n) = O(n * k)

  3. 平均情况:T(n) = O(n * k)

10.5 基数排序有两种方法:

  • MSD 从高位开始进行排序

  • LSD 从低位开始进行排序

  • 基数排序 vs 计数排序 vs 桶排序

    这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

    • 基数排序: 根据键值的每位数字来分配桶
    • 计数排序: 每个桶只存储单一键值
    • 桶排序: 每个桶存储一定范围的数值

举报

相关推荐

0 条评论