几种常见排序算法的基本介绍,性能分析,和c语言实现

本文介绍7种常见的排序算法,以及他们的原理,性能分析和c语言实现:

为了能够条理清楚,本文所有的算法和解释全部按照升序排序进行

首先准备一个元素无序的数组arr[],数组的长度为length,一个交换函数swap,

在main函数中实现排序函数的调用,并输出排序结果:

void swap(int*x , int*y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}
int main() {
    int arr[] = { 1,8,5,7,4,6,2,3};
    int length = sizeof(arr) / sizeof(int);
    sort(arr, length);
    for (int i = 0;i < length;i++) {
        printf("%d\n", arr[i]);
    }
    return 0;
}

插入排序

第一次循环:

第二次循环:

第三次循环:

外层循环每执行一次就从无序区向有序区中插入一个数据arr[i]

  里层循环控制插入的数据arr[i]与其前一个数据比较,如果比前一个数据小,就让前一个数据后移1位

  ...不断重复上述步骤,直到找到不比arr[i]小的数据arr[j],因为arr[j]后面的数据都后移了1位,所以直接将arr[i]放在空闲的arr[j+1]位置

c程序实现:

void CRsort(int arr[], int length) {
    int temp;
    for (int i = 0;i < length;i++) {
        temp = arr[i];
        for (int j = i - 1;j >= 0;j--) {
            if (arr[j] > temp) {
                arr[j + 1] = arr[j];
            }
            else {
                arr[j + 1] = temp;
                break;
            }
        }
    }
}

性能分析

稳定性 : 稳定             

  -->内层循环执行时,只有遇到大于arr[i]的才会后移,等于arr[i]的不会后移

时间复杂度 : (最坏n²,最好n,平均n²)    

  -->数据量为n的情况下,外层循环执行n次,内层循环最多执行n次,如果是数据是有序的内层循环只会执行1次

   数据越有序,插入排序的执行效率就越高

空间复杂度: 1

  -->程序没有用到递归,临时变量不占用存储资源,因此空间复杂度为1


希尔排序

希尔排序又叫"缩小增量排序",是插入排序的一种改进排本:

插入排序优点是适合处理接近有序的元素,缺点是每次只能比较一个元素,希尔排序利用了这两个特点:

区别于普通的插入排序,希尔排序有一个增量序列r,r的初始值一般取 int(length/2),也就是元素数量除以2向下取整

将所有相隔r个单位的元素组成一组,在组内完成排序

增量每次折半,直到最后一次排序时,增量为1,这时元素一定是有序的

最外层循环控制增量组,即每次循环的增量

  里面的两层循环就是一个最基本的插入排序,只不过每次加入有序组的不是arr[i+1],而是arr[i+增量]

 c程序实现:

void ShellSort(int arr[], int length) {
    int r, temp, j;
    for (r = length / 2;r >= 1;r = r / 2) {
        for (int i = r;i < length;i++) {
            temp = arr[i];
            j = i - r;
            while (j >= 0 && temp < arr[j]) {
                arr[j + r] = arr[j];
                j = j - r;
            }
            arr[j + r] = temp;
        }
    }
}

性能分析

稳定性 : 不稳定             

  -->每个元素都是在自己的排序组中排序,两个值相同的元素出于不同的排序组中他们总体的相对位置可能会发生变化

时间复杂度 : (最坏n²,最好n,平均n^1.3)    

  -->希尔排序的分析是一个复杂的问题,以为它的时间是所取“增量”序列的函数,这涉及到一些数学上尚未解决的难题,摘自网上.

   最好和普通插入排序相同,如果是数据是有序的内层循环只会执行1次

   同样拥有插入排序的特点:数据越有序,它的执行效率就越高

空间复杂度: 1

  -->程序没有用到递归,临时变量不占用存储资源,因此空间复杂度为1


冒泡排序

冒泡排序

外层循环每次都将一个元素置入有序区

  内层循环控制置入的元素:设置一个指针j,拿arr[j]的值与arr[j+1]进行比较:

  如果arr[j]<arr[j+1],就将arr[j]的值与arr[j+1]交换,否则j++,直到无序区比较完毕

值得一提的是,冒泡排序可以进行这样的优化:

设置一个状态码change,当有数据交换发生时change置1

如果一整次排序都没有交换发生,那么这组数据就是有序的,可以直接结束循环

c程序实现:

int MPsort(int arr[], int length) {
    int temp, change = 0;
    for (int i = 0;i < length;i++) {
        for (int j = 0;j < length - i - 1;j++) {
            if (arr[j] > arr[j + 1]) {
                swap(&arr[j],&arr[j+1]);
                change = 1;
            }
        }
        if (change == 0) {
            return 0;
        }
    }
}

性能分析

稳定性 : 稳定             

  -->数据只有在大于或小于时才会交换,相等时不会交换,因此相同数据的相对位置不会发生改变

时间复杂度 : (最坏n²,最好n,平均n²)    

  -->在数据完全有序时,不会有数据交换,根据上面的优化处理,状态码change不会改变,外层循环执行一次就结束,时间复杂度为n+1,也就是n.

    而如果是非常无序的数据,外层循环执行满n次,时间复杂度就是n²

空间复杂度: log(2)(N)

  -->程序没有用到递归,临时变量不占用存储资源,因此空间复杂度为1


快速排序

快速排序是一种效率比较高的排序方法,这张图片我认为介绍的很清楚,搬运来用一下,出处在文章下面:

c程序实现:

void KSSort(int arr[], int left, int right) {
    if (left < right) {
        int key = arr[left];
        int l = left;
        int r = right;
        while (l < r) {
            while (l < r && arr[r] >= key) {
                r--;
            }
            if (l < r) {
                arr[l] = arr[r];
                l++;
            }
            else {
                break;
            }
            while (l < r && arr[l] <= key) {
                l++;
            }
            if (l < r) {
                arr[r] = arr[l];
                r--;
            }
            else {
                break;
            }
        }
        arr[l] = key;
        KSSort(arr, left, l - 1);
        KSSort(arr, l + 1, right);
    }
}

性能分析

稳定性 : 不稳定 

  --> 假设是稳定的,举个反例: 

    5 | 3 1 2 | 9 7 8 9 | 4 6 3

    这时遍历unvisited部分 刚到了4 (array[8])

    显然4<5 ,这是4应该从 unvisited 部分去到 lower 部分。 因此 higher部分第一个元素 9 (array[4]) 和 4互换。变成了这样:

    5 | 3 1 2 4 | 7 8 9 9 | 6 3

时间复杂度 : (最坏n²,最好nlogn,平均nlogn)    

  -->快速排序最差的情况就是每次取到的基准数baes都是排序组的边界值(不是最小的就是最大的),这时外层循环要遍历n次才能将所有数据比较完,时间复杂度就是n²

    这种情况多发生在排好序(或接近排好序)的数据中,要在这种数据中避免使用快速排序算法

   最好的情况是基准数每次都能取到接近排序组的中位数,用最短的循环次数将程序完全分割,n个数据每次减半,也就是log(2)(N)次后数据被完全分割,算法的时间复杂度为

   最差的情况不容易取到,平均时间复杂度取nlogn

空间复杂度: logn

  -->在我这个程序里没有用到递归,空间复杂度为1,当然也可以使用递归实现,递归log(2)(N)次,空间复杂度为logn

最后附一张各种排序算法比较图


选择排序

选择排序是最简单的排序方式,也是比较低效的一种排序方式:

第一次循环 :

第二次循环:

第三次循环:

外层循环每执行一次向有序区添加一个元素

  内层循环遍历到最大的元素,与刚刚填入有序区的元素交换

......直到数据全部加入有序区

c程序实现:

void XZsort(int arr[] , int length) {
    int check;
    for (int i = 0;i < length - 1;i++) {
        check = i;
        for (int j = i + 1;j < length;j++) {
            if (arr[j] < arr[check]) {
                check = j;
            }
        }
        if (i != check) {
            swap(&arr[i],&arr[check]);
        }
    }
}

性能分析

稳定性 : 稳定

  -->存在两个相同的元素时,肯定是下标小的元素先进入有序区,而且在值相等的情况下有序区元素不会被替换

时间复杂度 : (最坏n²,最好n²,平均n²)

  -->外层循环要执行n次,才能将n个数据全部加入有序区

   无论数据是否有序,内层循环都要将所有的数据比较一遍,找到最小的元素

空间复杂度: 1

  -->程序没有用到递归,临时变量不占用存储资源,因此空间复杂度为1


堆排序

堆排序是选择排序的改进版本,它比堆排序的性能要高很多.

要实现堆排序,首先要了解这几个知识点:

大根堆:每个节点的值都大于他的左右子树(小根堆反之)

完全二叉树:除了最后一层之外的其他每一层都被完全填充,并且所有结点都保持向左对齐。

在n个节点的完全二叉树中,叶子节点有(n+1)/2个,非叶子节点有(n-1)/2个

在堆中,下标为n的数的左子树为2n+1,右子树为2n+2

首先要能够将一组无序的数排列成大根堆,大根堆的构造方法如下:

             

            

...

外层循环从最后一个非叶子节点( length/2-1 )向根节点遍历,每遍历到一个数据,就执行下面操作:

设置一个根指针i,指向当前要操作树的根节点

比较该树的左右子树的值,将biger指针指向较大的子树

如果该子树的值比根节点还大(arr[biger]>arr[i]),将该子树的值与根节点交换,并将i指针指向该子树,使之成为新的根节点

  ......递归执行上一步操作,直到有根节点不小于左右子树为止

构造出大根堆之后,每次取整个堆的根节点(也就是第一个元素)存入有序区,将堆的最后一个元素作为根节点

剩下的元素继续构造大根堆,直到数据完全存入有序区

c程序实现:

void MkHeap(int arr[], int i, int length) {
    int bigger = 2 * i + 1;
    int temp;
    if (bigger<length){
        if (arr[bigger] < arr[bigger + 1]) {
            bigger++;
        }
        if (arr[i]<arr[bigger]){//如果子树比爹树大:把子树的值与爹树值交换,并让该子树成为新的爹树
            swap(&arr[i], &arr[bigger]);
            MkHeap(arr,bigger,length);
        }
    }
}
void HeapSort(int arr[], int length) {
    //从最后一个非叶子节点往根找
    for (int i = length / 2 - 1;i >= 0;i--) {
        MkHeap(arr, i, length);
    }
    for (int j = length - 1;j > 0;j--) {
        swap(&arr[j],&arr[0]);
        MkHeap(arr, 0, j-1);
    }
}

性能分析

稳定性 : 不稳定             

  -->先假设稳定,然后举个反例:

   {6,7,7}这棵树左右子树的值都是7,本来左子树是应该是在前面的,但是6加入有序区之后右子树的7会被提到最上面,这样他们的顺序就被调换了

时间复杂度 : (最坏nlogn,最好nlogn,平均nlogn   <底数可以写2也可以不写,根据时间复杂度的性质无论底数是几结果都是一样的>   )    

  -->根据性质,外层循环会执行n次(n代表排序的元素个数),将所有数据全部加入有序区.

   而内层递归函数指针从0到n,每次增长前一个的2n+1,忽略掉常数1,也就是执行log(2)(N)次

   因此为Nlog(2)(N)次,忽略掉底数2,时间复杂度为nlogn

   插入/希尔排序适合接近有序的数据,而堆排序适合非常无序的数据,因为无论数据多么杂乱,希尔排序的时间复杂度都是nlogn

     (不放心再说一下 : log(2)(N)是log以2为底n的对数 , 不是log2乘n , 因为底数打不出来只能这样写)

空间复杂度: log(2)(N)

  -->这里程序递归了log(2)(N)次,每次递归都占用内存,因此空间复杂度为log(2)(N)

   当然也可以使用循环的方式实现,但是那样写出来结构略显混乱,不如递归方式清晰

不稳定算法口诀:快些选队

参考资料(文中的部分图片和思想来自以上材料):

1. 《新编数据结构习题与解析》

2. 文章

https://www.cnblogs.com/skywang12345/p/3603935.html

https://www.cnblogs.com/jingmoxukong/p/4302891.html

http://www.sohu.com/a/341037266_115128

https://www.toutiao.com/a6593273307280179715/?iid=6593273307280179715

3. 关于快速排序算法的稳定性? - 知遥其实是德鲁伊的回答:https://www.zhihu.com/question/45929062/answer/262452296

原文地址:https://www.cnblogs.com/iszhangk/p/11942617.html

时间: 2024-10-11 18:15:11

几种常见排序算法的基本介绍,性能分析,和c语言实现的相关文章

几种常见排序算法

几种常见排序算法 几种常见排序算法 写在前面 基础介绍 初级排序算法 selection sort选择排序 insertion sort插入排序 ShellSort希尔排序 shuffing不是排序算法 merge sort归并排序 Abstract in-place merge原地归并的抽象方法 Top-down mergesort自顶向下的归并排序 Bottom-up mergesort自底向上的归并排序 quicksort 三向切分的快速排序 Heapsort堆排序 总结和比较 命题 本文

JavaScript版几种常见排序算法

今天发现一篇文章讲“JavaScript版几种常见排序算法”,看着不错,推荐一下原文:http://www.w3cfuns.com/blog-5456021-5404137.html 算法描述: * 冒泡排序:最简单,也最慢,貌似长度小于7最优* 插入排序: 比冒泡快,比快速排序和希尔排序慢,较小数据有优势* 快速排序:这是一个非常快的排序方式,V8的sort方法就使用快速排序和插入排序的结合* 希尔排序:在非chrome下数组长度小于1000,希尔排序比快速更快* 系统方法:在forfox下系

Java中几种常见排序算法

日常操作中常见的排序方法有:冒泡排序.快速排序.选择排序.插入排序.希尔排序等. 冒泡排序是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成.这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端. * 冒泡法排序 * 比较相邻的元素.如果第一个比第二个大,就交换他们两个. * 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应

几种常见排序算法的总结

下面总结几种常见的排序算法,包括插入排序.选择排序.快速排序.归并排序和堆排序. 时间复杂度: 插入排序 选择排序 快速排序 归并排序 堆排序 Ο(n2) Ο(n2) Ο(nlog(n)) Ο(nlog(n)) Ο(nlog(n)) 算法概述: 插入排序:每次从未排好序的数据堆中拿出一个数,插入到已排好序的数据队列的正确位置. 选择排序:每次从未排好序的数据堆中找到最小的数,插入到已排好序的数据队列的头部. 快速排序:以数据堆中的一个数为标准,将数据堆分为小于等于和大于该数的两堆,对于分割后的两

Java实现9种常见排序算法

常见的排序算法有冒泡排序.选择排序.插入排序.堆排序.归并排序.快速排序.希尔排序.基数排序.计数排序,下面通过Java实现这些排序 1.冒泡排序 package com.buaa; import java.util.Arrays; /** * @ProjectName SortingAlgorithm * @PackageName com.buaa * @ClassName BubbleSort * @Description 冒泡排序 * @Author 刘吉超 * @Date 2016-05

php几种常见排序算法

<?php //从时间上来看,快速排序和归并排序在时间上比较有优势,//但是也比不上sort排序,归并排序比较占用内存! $arr = [4,6,1,2,3,89,56,34,56,23,65];/** * 冒泡排序 * 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来. * 走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成 */function BubbleSortq($arr) { $len = count($arr); //设置一个空

几种常见排序算法的C++描述

1.首先当然是插入排序啦,算法的时间复杂度其实是O(N^2)的 先来个一般的描述: 1 template<typename Comparable> 2 void insertSort(vector<Comparable> & a) 3 { 4 int j; 5 for (int p = 1; p < a.size(); p++) 6 { 7 Comparable tmp = a[p]; 8 for (int j = p - 1; j > 0 &&

5种常见排序算法原理&amp;C语言实现

一.冒泡排序(以下各法均以从小到大排序为例,定义len为数组array的长度) 原理:比较相邻元素的大小,对于每次循环,按排序的规则把最值移向数组的一端,同时循环次数依次减少. C代码实现 写法一: //从数组头部开始,某一个元素与其后面的每个元素作比较for(i=0;i<len-1;i++) for(j=i+1;j<len;j++) { if(array[i]>array[j]) { t=array[i];array[i]=array[j];array[j]=t; } } 写法二: /

C语言中的几种常见排序算法

1.冒泡排序法:如果有N个数,两两相邻进行比较,将最值沉入最后一位置,要进行N-1轮比较, 第一轮要比较N-1次,第2轮只要比较N-1-1次,因为第一次已经把最值沉入最后一位置,故 不需在进行比较一次,也就是说每一轮都减少一次,所以第2个for循环中j < n-1;下列代码是升序排序 1 void bubble_sort(int *num, int n) 2 { 3 int num_temp; 4 for (int i=0; i<n; i++) 5 for (int j=0; j<n-i