赞
踩
该篇大部分内容属于转载,并在此基础上修改和添加
博客来源:https://www.cnblogs.com/onepixel/articles/7674659.html
目录
0.1 算法分类
十种常见排序算法可以分为两大类:
0.2 算法复杂度
(此图有误,错误在堆的空间复杂度))
(来源)
0.3 相关概念
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
1.1 算法描述
1.2 动图演示
1.3 代码实现
- #include <stdio.h>
- #define SWAP(x,y,t) {t=y;y=x;x=t;}
- //只需替换以下函数即可测试
- void BubbleSort(int a[], int n){
- int i, j, temp, flag;
- for (i = n-1; i>0; --i){ //循环的趟数,n-1趟,n-1-->n-1+1-1
- flag = 0;
- for (j = 0; j<i; ++j){ //j<i是因为下标从0开始与实际位置差一
- if (a[j] > a[j+1]){
- temp = a[j];
- a[j] = a[j+1];
- a[j+1] = temp;
- flag = 1;
- }
- }
- if (!flag) return;
- }
- }
- void BubbleSort2(int a[], int n){
- int i, j, temp, flag;
- for (i = 0; i<n-1; ++i){
- flag = 0;
- for (j = 0; j<n-i-1; ++j){
- if (a[j] > a[j+1]){
- temp = a[j];
- a[j] = a[j+1];
- a[j+1] = temp;
- flag = 1;
- }
- }
- if (!flag) return;
- }
- }
- int main(){
- int a[5] = {8,2,6,5,4};
- BubbleSort(a,5); //函数名别忘了
- for (int i=0; i<5; ++i)
- printf("%d ", a[i]);
- }
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
2.1 算法描述
n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:
2.2 动图演示
2.3 代码实现
- void SelectSort(int a[], int n){
- int temp, min, i, j;
- for (i=0; i<n; ++i){ //所有元素都要遍历
- min = i;
- for (j=i+1; j<n; ++j) //记不住也可以j=i; +1是因为本身不用比较
- if (a[min] > a[j])
- min = j;
- temp = a[i];
- a[i] = a[min];
- a[min] = temp;
- }
- }
- //注意每个排序的for循环的开始,选择排序是i=0
- //插入排序是i=1,因为第一个必定有序 可以跳过
2.4 算法分析
表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
3.1 算法描述
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
3.2 动图演示
3.2 代码实现
- void InsertSort(int a[], int n){ //直接插入
- int i, j;
- int temp;
- for (i = 1; i<n; ++i){ //a[0]不用比较,就一个
- if (a[i] < a[i-1]){
- temp = a[i];
- for (j=i; j>=1 && a[j-1] > temp; --j)
- a[j] = a[j-1]; //只要temp小于就一直往前移动,注意j≥1即可
- a[j] = temp;
- }
- }
- }
- void BinaryInsertSort(int a[], int n){ //折半插入
- int i, j, low, high, mid, temp;
- for (i=0; i<n; ++i){
- low = 0;
- high = i-1;
- temp = a[i];
- while (low <= high){
- mid = (low+high)/2;
- if (a[mid] > temp)
- high = mid - 1;
- else
- low = mid + 1;
- }
- for (j=i-1; j>=high+1; --j) //将i之前的元素后移
- a[j+1] = a[j];
- a[j+1] = temp; //在j+1处插入
- }
- }
- https://www.cnblogs.com/sunnylux/p/11039412.html
- https://segmentfault.com/a/1190000013564889
3.4 算法分析
插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
4.1 算法描述
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
4.2 动图演示
4.3 代码实现
- void ShellSort(int a[], int n){
- int i, j, d, temp;
- for (d = n/2; d>0 ; d /= 2){
- for (i = d; i<n; ++i){ //是不是跟上面的插排很像,只不过1->d
- if (a[i-d] > a[i]){
- temp = a[i];
- for (j = i; j>=d && temp<a[j-d]; j -= d)
- a[j] = a[j-d];
- a[j] = temp;
- }
- }
- }
- }
- //中间这个j循环,就是比较增量序列中的值,
- //第二次,序列中有两个元素,首先temp是增量序列最右边的,所以只要后面的大,则进行交换。
- //且每次排完都是!有序!的:
- //可以这样子想,第一次i=d只有一个,第二次i=2d如果大则交换,同时序列有序,
- //第三次i=3d,如果temp小则跟!直接插入!一样,一此次在有序序列中比较。
- //(这也是为什么增量序列中用直接插入的原理,
- //可以画一下图理解,文字阐述不太容易清楚或者在我的题目中看看)
- //https://blog.csdn.net/Z_timer/article/details/110929131
- //排序填空题第四个
4.4 算法分析
希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版)》的合著者Robert Sedgewick提出的。
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
5.1 算法描述
5.2 动图演示
5.3 代码实现
- //搬自原文
- function mergeSort(arr) {
- varlen = arr.length;
- if(len < 2) {
- returnarr;
- }
- varmiddle = Math.floor(len / 2),
- left = arr.slice(0, middle),
- right = arr.slice(middle);
- return merge(mergeSort(left), mergeSort(right));
- }
-
- function merge(left, right) {
- varresult = [];
-
- while(left.length>0 && right.length>0) {
- if(left[0] <= right[0]) {
- result.push(left.shift());
- } else{
- result.push(right.shift());
- }
- }
-
- while(left.length)
- result.push(left.shift());
-
- while(right.length)
- result.push(right.shift());
-
- return result;
- }
- //另一篇博客代码:https://www.cnblogs.com/DSNFZ/articles/7745785.html
5.4 算法分析
归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。
5.5 我的想法
多路排序:设需要的路数为m,则对于n个关键字做m路归并排序所需趟数S=
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
6.1 算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
6.2 动图演示
6.3 代码实现
- void QuickSort(int a[], int left, int right){ //递归实现
- int i, j, temp, pivot;
- if (left < right){
- i = left; j = right+1; //i第一个枢轴不用比较,所以do.while可以,
- //j必须先+1才可以do.while否则缺少最后一个元素。
- pivot = a[left];
- do {
- do ++i;
- while (pivot > a[i]);
- do --j;
- while (pivot < a[j]);
- if (i<j) SWAP(a[i], a[j], temp);
- }while (i<j);
- SWAP(a[left], a[j], temp); //交换 使其左大右小
- QuickSort(a, left, j-1);
- QuickSort(a, j+1, right);
- }
- }
-
- int Partition(Sqlist &L, int left, int right ){ //非递归快排 记得改成数组
- int i = left, j = right, temp;
- int pivot = L.data[i];
- while (i < j){
- while (i < j && pivot <= L.data[j]) --j; //不加=若顺序表出现重复数 陷入死循环
- if (i < j)
- L.data[i] = L.data[j];
- while (i < j && pivot > L.data[i]) ++i;
- if (i < j)
- L.data[j] = L.data[i];
- }
- L.data[j] = pivot; //交换 使其左大右小
- return j;
- }
-
- void QuickSort(Sqlist &L, int left, int right ){ //递归调用非递归
- if( left >= right ) return ;
- int index = Partition(L, left, right ) ;
- QuickSort(L, left, index-1 ) ;
- QuickSort(L, index+1, right ) ;
- }
- // https://blog.csdn.net/u013457167/article/details/79749882 非递归快排应用
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
7.1 算法描述
7.2 动图演示
7.3 代码实现
- // 摘自原文
- var len; // 因为声明的多个函数都需要数据长度,所以把len设置成为全局变量
-
- function buildMaxHeap(arr) { // 建立大顶堆
- len = arr.length;
- for(var i = Math.floor(len/2); i >= 0; i--) {
- heapify(arr, i);
- }
- }
-
- function heapify(arr, i) { // 堆调整
- var left = 2 * i + 1,
- right = 2 * i + 2,
- largest = i;
- if(left < len && arr[left] > arr[largest]) {
- largest = left;
- }
-
- if(right < len && arr[right] > arr[largest]) {
- largest = right;
- }
-
- if(largest != i) {
- swap(arr, i, largest);
- heapify(arr, largest);
- }
- }
-
-
- function swap(arr, i, j) {
- var temp = arr[i];
- arr[i] = arr[j];
- arr[j] = temp;
- }
-
-
- function heapSort(arr) {
- buildMaxHeap(arr);
- for(vari = arr.length - 1; i > 0; i--) {
- swap(arr, 0, i);
- len--;
- heapify(arr, 0);
- }
- return arr;
- }
计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
8.1 算法描述
8.2 动图演示
8.3 代码实现
- //摘自原文
- function countingSort(arr, maxValue) {
- var bucket =newArray(maxValue + 1),
- sortedIndex = 0;
- arrLen = arr.length,
- bucketLen = maxValue + 1;
- for(var i = 0; i < arrLen; i++) {
- if(!bucket[arr[i]]) {
- bucket[arr[i]] = 0;
- }
- bucket[arr[i]]++;
- }
-
- for(var j = 0; j < bucketLen; j++) {
- while(bucket[j] > 0) {
- arr[sortedIndex++] = j;
- bucket[j]--;
- }
- }
- return arr;
- }
8.4 算法分析
计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
9.1 算法描述
9.2 图片演示
9.3 代码实现
- // 摘自原文
- function bucketSort(arr, bucketSize) {
- if(arr.length === 0) {
- returnarr;
- }
- var i;
- var minValue = arr[0];
- var maxValue = arr[0];
- for(i = 1; i < arr.length; i++) {
- if(arr[i] < minValue) {
- minValue = arr[i]; // 输入数据的最小值
- }else if(arr[i] > maxValue) {
- maxValue = arr[i]; // 输入数据的最大值
- }
- }
- // 桶的初始化
- var DEFAULT_BUCKET_SIZE = 5; // 设置桶的默认数量为5
- bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
- var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
- var buckets = newArray(bucketCount);
- for(i = 0; i < buckets.length; i++) {
- buckets[i] = [];
- }
- // 利用映射函数将数据分配到各个桶中
- for(i = 0; i < arr.length; i++) {
- buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
- }
- arr.length = 0;
- for(i = 0; i < buckets.length; i++) {
- insertionSort(buckets[i]); // 对每个桶进行排序,这里使用了插入排序
- for(var j = 0; j < buckets[i].length; j++) {
- arr.push(buckets[i][j]);
- }
- }
- return arr;
- }
9.4 算法分析
桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
10.1 算法描述
10.2 动图演示
10.3 代码实现
- // 摘自原文
- var counter = [];
- function radixSort(arr, maxDigit) {
- var mod = 10;
- var dev = 1;
- for(var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
- for(var j = 0; j < arr.length; j++) {
- var bucket = parseInt((arr[j] % mod) / dev);
- if(counter[bucket]==null) {
- counter[bucket] = [];
- }
- counter[bucket].push(arr[j]);
- }
- var pos = 0;
- for(var j = 0; j < counter.length; j++) {
- var value =null;
- if(counter[j]!=null) {
- while((value = counter[j].shift()) !=null) {
- arr[pos++] = value;
- }
- }
- }
- }
- return arr;
- }
10.4 算法分析
基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。
基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。