这里写目录标题

  • 1.冒泡排序
    • 1.1 冒泡排序的思想
    • 1.2 冒泡排序的实现
  • 2.选择排序
    • 2.1 选择排序的思想
    • 2.2 选择排序的实现
  • 3.直接插入排序
    • 3.1 直接插入排序的思想
    • 3.2 直接插入排序的实现
  • 4.希尔排序(增量排序)
    • 4.1 希尔排序的思想
    • 4.2 希尔排序的实现
  • 5.堆排序
    • 5.1 基础概念
      • 5.1.1 二叉树的概念
      • 5.1.2 满二叉树的概念
      • 5.1.3 完全二叉树
      • 5.1.4 堆的概念
      • 5.1.5 二叉树的存储方式
    • 5.2 堆的建立过程
    • 5.3 堆排序
  • 6.快速排序
    • 6.1 快排的思想
    • 6.2 代码的实现(递归)
    • 6.3 非递归的实现
  • 7.二路归并排序
    • 7.1 二路归并排序的思想
    • 7.2 二路归并排序的代码实现
  • 8.基数排序
    • 8.1 基数排序的思想
    • 8.2基数排序的代码实现
  • 9.各排序算法的性能总结

排序算法评判的标准
1.时间复杂度
2.空间复杂度
3.稳定性:在整个排序过程中,有没有跳跃式的交换数据。如果有,则排序算法不稳定,如果没有,则排序算法是稳定的

辅助方法代码

void ShowArr(int* arr, int len) //显示数组
{for (int i = 0; i < len; ++i){printf("%d  ", arr[i]);}printf("\n");
}bool IsSort(int* arr, int len) //判断数据是否有序,从小到大
{for (int i = 0; i < len - 1; ++i) {if (arr[i] > arr[i + 1])//前一个比后一个大return false;}return true;
}void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}

排序算法的稳定性:在整个排序过程中,有没有跳跃式的交换数据。如果有,则排序算法不稳定,如果没有,则排序算法是稳定的

1.冒泡排序

1.1 冒泡排序的思想

一趟排序的过程,将相邻的两个元素进行比较,如果前一个比后一个大,则将两个元素交换–将最大的元素交换到整个数据的最后。
排序的趟数:数据元素的个数:len - 1

1.2 冒泡排序的实现

/*时间复杂度:O(n^2)空间复杂度:O(1)稳定性:稳定
*/
void BubbleSort1(int* arr, int len)
{   for (int i = 0; i < len - 1; ++i){//一趟循环结束后,最大的数据被排到最后for (int j = 0; j < len - 1 - i; ++j)//len - 1 - i 每经过一趟排序,下一次就可以少比较一个数据{if (arr[j] > arr[j + 1]){Swap(&arr[j], &arr[j + 1]);}}}
}
void BubbleSort(int* arr, int len)//优化后
{for (int i = 0; i < len - 1; ++i){bool flag = true;for (int j = 0; j < len - 1 - i; ++j){if (arr[j] > arr[j + 1]){Swap(&arr[j], &arr[j + 1]);flag = false;}}//一趟循环走完后,flag还为真,说明本来数据就有序,没有进行交换if (flag)return;}
}

2.选择排序

2.1 选择排序的思想

1.先遍历一遍整个待排序数据,找到当前最大的值的位置
2.将标记的最大值与当前最后一个元素交换。
3.重复上述过程,直到只剩下一个数据
上面的步骤每做一次,下一次就少遍历一个数据。(本次找到的最大值)
选择排序执行的趟数:数据元素的个数 len - 1

2.2 选择排序的实现

/*时间复杂度:O(n^2)空间复杂度:O(1)稳定性:不稳定
*/
void SelectSort(int* arr, int len)
{       for (int i = 0; i < len - 1; ++i){    int max_index = 0;  //最大值的下标for (int j = 0; j < len - i; ++j){if (arr[j] > arr[max_index]){max_index = j;}}Swap(&arr[max_index], &arr[len - 1 - i]);  //将标记的最大值与当前最后一个元素交换}
}

3.直接插入排序

3.1 直接插入排序的思想

1.将数据分为两部分,前一段是已经有序的数据(初始时,前一段数据只有一个),后一段是无序数据。
2.从无序数据中拿一个数据(拿左边的第一个数据),插入到前一段的有序数据中,并且使其依旧有序。
3.重复上述过程,直到无序数据段没有数据。

3.2 直接插入排序的实现

/*时间复杂度:O(n^2)空间复杂度:O(1)稳定性:稳定
*/
void InsertSort(int* arr, int len)
{for (int i = 1; i < len; ++i)        // i 负责遍历无序数据段{int tmp = arr[i];int j = 0;for (j = i - 1; j >= 0; --j){if (arr[j] <= tmp)break;arr[j + 1] = arr[j];}arr[j + 1] = tmp;}
}

直接插入排序分析进阶:
思考:如果数据已经有序,使用直接插入排序,时间复杂度是多少?
如果数据已经有序,那么直接插入排序的内层循环就是一个常语句,所以时间复杂度就为O(n)
结论:数据越有序,直接插入排序速度越快,时间复杂度趋近于O(n)

4.希尔排序(增量排序)

4.1 希尔排序的思想

1.先将数据分成 d 个组,在每个分组内使用直接插入排序算法进行排序。目的就是让整个数据序列越来越有序

2.接着将数据继续分组(分组数越来越小),然后排序,最后一次分组肯定为 1 组。每次分组的组数一般是互质的。

4.2 希尔排序的实现

void Shell(int* arr, int len, int group)
{for (int i = group; i < len; ++i)        // i 负责遍历整个数据段,控制本次直接插入排序处理那一组的数据{int tmp = arr[i];int j = 0; for (j = i - group; j >= 0; j -= group){if (arr[j] <= tmp)break;arr[j + group] = arr[j];}arr[j + group] = tmp;}
}
/*时间复杂度:O(n^1.3 ~ n^1.5) 数学推导得出空间复杂度:O(1)稳定性:不稳定
*/
void ShellSort(int* arr, int len)
{int group[] = { 5, 3, 1};     //分组没有标准的分法,一般组数我们自己设置,数据量大我们可以自己加for (int i = 0; i < sizeof(group) / sizeof(group[0]); ++i){Shell(arr, len, group[i]);}
}

5.堆排序

5.1 基础概念

5.1.1 二叉树的概念

二叉树的概念:二叉树(binary tree)是指树中节点的不大于2的有序树。在二叉树中除了根节点的每一个节点都仅有一个父节点,所有的节点最多只能有两个孩子节点。

5.1.2 满二叉树的概念

(1)除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。
(2)国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
(3)国外(国际)定义:a binary tree T is full if each node is either a leaf or possesses exactly two childnodes.
大意为:如果一棵二叉树的结点要么是叶子结点,要么它有两个子结点,这样的树就是满二叉树。(一棵满二叉树的每一个结点要么是叶子结点,要么它有两个子结点,但是反过来不成立,因为完全二叉树也满足这个要求,但不是满二叉树) ||不够准确

5.1.3 完全二叉树

对满二叉树的结点进行编号, 约定编号从根结点起, 自上而下, 自左而右。则深度为k的, 有n个结点的二叉树, 当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时, 称之为完全二叉树

5.1.4 堆的概念

堆通常是一个可以被看做一棵完全二叉树。
a.大根堆,就是说这个完全二叉树中的每一棵子树的根节点都大于它的左右孩子(如果有的话)
b.小根堆:就是说这个完全二叉树中的每一棵子树的根节点都小于它的左右孩子(如果有的话)

5.1.5 二叉树的存储方式

(1)顺序存储

  • List item按照完全二叉树(无论是哪种二叉树)的形式,将二叉树的数据全部存储在数组中。
  • 顺序表中的每个元素除了存储(data,leftchild,rightchild)

(2)链式存储:每个节点都有至少三个域(data,leftchild,rightchild)

堆仅仅将数字存储在数组中

1.如果父节点的下标为 i ,则左孩子的下标为 2*i+1,右孩子的下标为2*i+2
2.如果孩子的下标为 j ,则父节点的下标为 (j - 1)/2

5.2 堆的建立过程

1.将数组中的数据构造成堆时,从最后一棵子树开始,想根节点一棵棵调整。
2.每棵子树的调整都是从其根节点开始向下调整的。

一棵子树的调整过程
先用 i 指向子树的根节点, j 指向根节点的左孩子。将 i 位置的值保存到临时变量(tmp)中,**然后在左右孩子中找到较大的那一个,j 指向较大值,(1)如果较大的那一个比临时变量中的值小,则直接退出。(2)如果较大的那一个比临时变量值大,则将较大的值保存到当前的 i 位置上,然后指向父节点的变量就为较大值的下标(即 i = j,j = 2*i+1)。直到左孩子的下标( j )越界退出。退出以后,将tmp的值保存退出时的父节点位置。

代码实现:

//O(log(n))
void OneAdjust(int* arr, int len, int root)
{int tmp = arr[root];int i = root;int j = 2 * i + 1;while (j < len){if (j + 1 < len && arr[j] < arr[j + 1])      // j + 1 < len 成立,则有右孩子。arr[j] < arr[j+1]左孩子小于右孩子j++;if (arr[j] < tmp)break;arr[i] = arr[j];i = j;j = 2 * i + 1;}arr[i] = tmp;
}

将数组调整成堆的过程
先找到最后一棵子树的根节点,向根节点递减,循环调用上面的调整方法。

代码实现:

//O(nlog(n))
void CreateHeap(int* arr, int len)
{int lastRoot = (len - 2) / 2;//((len - 1) - 1) / 2 /*lastRoot就是最后一棵子树的根节点下标for ( int i = lastRoot; i >= 0; --i) //从lastRoot开始,循环到整棵树的根节点,调整{OneAdjust(arr, len, i);}
}

5.3 堆排序

先将数组中的数据调整成一棵最大堆,然后将根节点的数据和最后位置的节点交换,接着除过最后一个位置的数据外,再将剩余的数据调整成最大堆。
重复上述过程,直到只剩下一个数据没有交换。就完成了堆排序。

代码实现:

/*时间复杂度:O(nlog(n))空间复杂度:O(1)稳定性:不稳定
*/
void HeapSort(int* arr, int len)
{CreateHeap(arr, len);for (int i = 0; i < len - 1; ++i){Swap(&arr[0], &arr[len - 1 - i]);OneAdjust(arr, len - i - 1, 0);}
}

扩展应用:

1.找倒数第 k 大或者倒数第 k 小的数据。
2.只需要关注最小的或者最大的值的场景。

6.快速排序

6.1 快排的思想

  1. 先在数据中找一个基准数据:找待排序数据序列的第一个,随机数,三位数取中。
  2. 将数据按照基准数据分成两部分,前一段都比基准小,后一段都比基准大。
  3. 按照同样的思想分别处理前一段和后一段数据。

思考的两个问题:

  1. 处理一段数据时,什么样的情况下就认为这一段数据已经完全有序。
  2. 如何将数据按照基准分成两部分。

6.2 代码的实现(递归)

一、按照一个基准数据,将待排序数据段分成两部分

数据段为 start 到 end 之间的数据。先将基准数据(start 位置)保存到 tmp 中,i = start ,j = end 。在 i < j 的时候,重复执行以下两个过程。

  1. 通过 j 从后往前找第一个比基准数据小的数据,找到后就将 j 位置的值赋值到 i 位置。
  2. 通过 i 从前往后找第一个比基准数据大的数据,找到后就将 i 位置的值赋值到 j 位置。
    循环退出( i == j )后,将 tmp 中的基准数据存储在 i 位置。此时,数据就被分成了前后两部分。

代码:

//根据基准值,找到基准数据应该所在的位置,将数据在原始的空间中划分成两部分
int OneQuick(int* arr, int start, int end)
{int tmp = arr[start];     //基准数据int i = start, j = end;while (i < j){//从后往前找比基准大的数据while (i < j && arr[j] > tmp)   j--;arr[i] = arr[j];//从前往后找比基准小的数据while (i < j && arr[i] <= tmp)    i++;arr[j] = arr[i];}arr[i] = tmp;return i;
}

二、处理前一段和后一段数据 — 递归

判断一段数据是否已经处理完的依据就是看这个段中还有几个数据,只要超过一个数据,那就认为并没有处理完成。

代码:

/*时间复杂度:O(nlog(n))空间复杂度:O(log(n))稳定性:不稳定
*/
void Quick(int* arr, int start, int end)        //O(nlog(n))
{//退出的条件if (end - start < 1)     return;else{int mid = OneQuick(arr, start, end);Quick(arr, start, mid - 1);        //递归处理前一段数据Quick(arr, mid + 1, end);       //递归处理后一段数据}
}void QuickSort(int* arr, int len)
{Quick(arr, 0, len - 1);
}

6.3 非递归的实现

将Quick方法改为非递归(循环实现)。但是循环体中一次只能处理一部分数据,那剩余部分数据的 start,end 这一对数据就需要通过其他的数据结构(栈和队列)将其保存。

结构声明
将要存储的数据段的起始位置(start)和结束位置(end)封装成(struct)一个数据。

typedef struct
{int start;int end;
}ElemType;//这个类型就是将来要给栈或者队列中插入的数据。//空间复杂度:O(log(n))    栈使用的空间决定的
//非递归实现
void Quick(int* arr, int start, int end)
{Stack st;InitStack(&st);ElemType val = { start, end };Push(&st, val);     //将整个数据段的起始和结束位置Push到栈中while (!Empty(&st)){ElemType data;Top(&st, &data);Pop(&st);int mid = OneQuick(arr, data.start, data.end);if (mid - data.start > 1)   //mid - 1 - data.start > 0       //基准的前一个数据减起始位置的数据,只剩一个数据说明这段数据处理完了(已经有序){val.start = data.start;val.end = mid - 1;Push(&st, val);     //mid这个基准数据左边有超过1个的数据压栈}if (data.end - mid > 1)  //data.end - (mid + 1) > 0      //后一段数据{val.start = mid + 1;val.end = data.end;Push(&st, val);       //mid这个基准数据右边有超过1个的数据压栈}}DestroyStack(&st);
}

如果在一个工程中使用了其他工程的代码
1.设置工程的属性,在工程中引入其他的工程。将要引入的工程的配置类型改为静态库。
2.在使用另一个工程的代码的源文件中直接使用 inculde 引入另一个工程的.cpp文件 (一般不使用)

6.4 快速排序的优化

1.用随机数来选取基准数据。
2.用三位数取中法(第一个数,最后一个数和中间那个数的中位数)选取基准数据
3.当代排序序列数据量达到一定数量时,使用插入排序
4.聚集相等元素

7.二路归并排序

7.1 二路归并排序的思想

一次排序过程,将已经各自有序的两个段的数据合并成一个段,并且合并后依旧有序。
第一次我们认为单个数据是有序的,一个数据一个段,一次排序后,两个数据就是一个有序数据段,这样下一次每个有序数据段就有两个数据,最后将其合并成一个完全有序段,则整个数据就已经排序好了。

7.2 二路归并排序的代码实现

void Meger(int* arr, int len, int width, int* brr)
{int low1 = 0;int high1 = low1 + width - 1;      //high1为第一个段的结束位置下标int low2 = high1 + 1;int high2 = low2 + width > len ? len - 1 : low2 + width - 1;int* brr = (int*)malloc(sizeof(int) * len);assert(brr != NULL);int index = 0;//处理有两个归并段while ( low2 < len){//两个归并段都有未归并数据while (low1 <= high1 && low2 <= high2){if (arr[low1] < arr[low2])     brr[index++] = arr[low1++];else brr[index++] = arr[low2++];}//只剩下一个归并段数据,将两个归并段剩余的数据复制到brr中//只会执行其中一个while循环while (low1 <= high1)          brr[index++] = arr[low1++];while (low2 <= high2)           brr[index++] = arr[low2++];low1 = high2 + 1;high1 = low1 + width - 1;low2 = high1 + 1;high2 = low2 + width > len ? len - 1 : low2 + width - 1;}//处理只剩下一个归并段的情况while (low1 < len)        brr[index++] = arr[low1++];//将brr中的数据全部复制回arr中for (int i = 0; i < len; i++)      arr[i] = brr[i];}void MegerSort(int* arr, int len)
{int* brr = (int*)malloc(sizeof(int) * len);assert(brr != NULL);// i 就是每个段当前的数据个数for (int i = 1; i < len; i *= 2){Meger(arr, len, i, brr);}free(brr);
}

8.基数排序

8.1 基数排序的思想

针对于有多个关键字的排序算法,先按照一个关键字排序,完成后再按照另一个关键字排序。先按照权重小的关键字排序,再按照权重大的关键字排序。
按照每个关键字值的范围,定义相同数量的队列。对于数字我们先按照个位进行排序,然后再按照十位进行排序

8.2基数排序的代码实现

//获取最大数据的位数
int GetDigit(int* arr, int len)
{int max = arr[0];for (int i = 0; i < len; i++){if (max < arr[i])     max = arr[i];}int digit = 0;while (max){digit++;max /= 10;}return digit;
}//获取一个数据相应位数上的值
int GetRadix(int val, int digit)
{//val 1234int radix = val % 10;   //4while (digit){val /= 10;        //123radix = val % 10; //3digit--;}return radix;
}/*时间复杂度:O(d*n)  d是关键字的个数空间复杂度:O(n)稳定性:稳定
*/
void RadixSort(int* arr, int len)
{int maxDigit = GetDigit(arr, len);ListQue que[10];for (int i = 0; i < 10; i++){InitListQue(&que[i]);}//根据不同的位数,处理整个数据for (int i = 0; i < maxDigit; i++){//将arr中的所有数据取其 i 这个位数的值,并且插入到相应值的队列中for (int j = 0; j < len; j++){int radix = GetRadix(arr[j], i);Push(&que[radix], arr[j]);}//将所有的队列按照从前到后的顺序把值全部出到arr中int index = 0;for (int k = 0; k < 10; k++){while (!Empty(&que[k])){GetHead(&que[k], &arr[index++]);Pop(&que[k]);}}}for (int i = 0; i < 10; i++){DestroyListQue(&que[i]);}
}

9.各排序算法的性能总结

C语言八大排序算法【详细 代码+图文】相关推荐

  1. C语言八大排序算法,附动图和详细代码解释!

    文章来源:电子工程专辑.C语言与程序设计.竹雨听闲 一.前言 如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功. 想写出精炼.优秀的代码,不通过不断的锤炼,是很难做到的. 二. ...

  2. 硬核!C语言八大排序算法,附动图和详细代码解释!

    来源 :C语言与程序设计.竹雨听闲等 一 前言 如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功. 想写出精炼.优秀的代码,不通过不断的锤炼,是很难做到的. 二 八大排序算法 ...

  3. c语言的八大排序算法,程序员的内功:C语言八大排序算法

    四 一.冒泡排序 冒泡排序算法的运作如下: ●比较相邻的元素.如果第一个比第二个大,就交换他们两个. ●对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.这步做完后,最后的元素会是最大的数. ...

  4. 序列划分c语言,一篇“get”C语言八大排序算法

    如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功. 想写出精炼.优秀的代码,不通过不断的锤炼,是很难做到的. 二.八大排序算法 排序算法作为数据结构的重要部分,系统地学习一下是 ...

  5. 八大排序算法——(万字图文详解)

    本篇文章是我对之前写过的八个排序算法的总结,感兴趣的小伙伴可以去我的八大排序算法专栏浏览,也可以点击下方标题跳转. 提示:本篇博客篇幅较长,建议小伙伴们查看目录,按需浏览 目录 正文 1 直接插入排序 ...

  6. 冒泡排序c++代码_八大排序算法(解释+代码+结果+算法优化)

    >>>欢迎点赞,收藏,转发! 评论区获取源代码与更多更全干货!<<< 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,不需要访问外存便能完成 ...

  7. 数据结构 常用八大排序算法及代码实现

    一.排序的介绍 1. 排序的分类 按照排序过程中所依据的原则的不同可以分类为: ►插入排序:直接插入排序  希尔排序 ►交换排序:冒泡排序  快速排序 ►选择排序:简单选择排序  堆排序 ►归并排序 ...

  8. 图解八大排序算法——我见过的最详细的讲解(转)

    一.分类 1.内部排序和外部排序 内部排序:待排序记录存放在计算机随机存储器中(说简单点,就是内存)进行的排序过程. 外部排序:待排序记录的数量很大,以致于内存不能一次容纳全部记录,所以在排序过程中需 ...

  9. 八大排序算法图文讲解

    排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 常见的内部排序算法有:插入排序.希尔排序. ...

最新文章

  1. win8数据源设置mysql_Win8系统ODBC数据源有何重要功能?
  2. 转vc++ 类自己消失
  3. Java 连接 MS sql Server 2005
  4. 【Android APT】注解处理器 ( 根据注解生成 Java 代码 )
  5. 【阿里云课程】深度生成模型基础,自编码器与变分自编码器
  6. ASP.NET MVC 线程和并发
  7. hibernate 联合主键 merge
  8. Nmap Windows 版本时区显示乱码
  9. Document is invalid: no grammar found. at (null:3:8)
  10. 【渝粤教育】国家开放大学2018年春季 0093-21T刑法 参考试题
  11. googlenet网络结构_CNN网络结构的发展
  12. 前端上传插件Plupload的实际使用(个人实操)
  13. Spring、SpringMVC、SpringBoot、SpringCloud概述
  14. 常量表达式(constexpr)
  15. 迷宫~哈哈~终于懂了BFS
  16. 洛谷1008 三连击
  17. 钉钉直播html,钉钉直播功能介绍,钉钉直播步骤详情
  18. 【博文视点送书福利】Java程序员也应该会点产品经理的活!另外还要有小程序哦!
  19. 【一名合格前端工程师的自检清单】--自检答案
  20. 478-82(56、128、718、129)

热门文章

  1. jvisualvm连接tomcat
  2. 火狐浏览器的几个比较好的,最常用的几款插件。
  3. 找地震测线剖面注意事项
  4. 【GPT4】微软 GPT-4 测试报告(5)与外界环境的交互能力
  5. 安全测试工具之Hping3
  6. Java自定义生成与解析(Dom4J)XML文件
  7. STM32F103C8T6+带FIFO的OV7670的输出测试图像实例代码
  8. 怎么编辑图片尺寸?如何编辑照片大小?
  9. VS2017 CUDA编程学习4:CUDA并行处理初探 - 向量加法实现
  10. 向日葵Linux版的安装与使用