内部排序算法
来源:互联网 发布:淘宝助理在哪下载安装 编辑:程序博客网 时间:2024/05/21 12:01
本文记录了我在面试过程中感觉有用的问题,方便日后参考。
前言
一、排序的目的
为了查找方便,通常希望计算机中的查找表是按关键字有序的,因为此时可以使用查找效率较高的折半查找。
二、排序的概念
将一个数据元素的任意序列,重新排列成一个按关键字有序的序列。
三、内部排序和外部排序
根据排序时待排序的数据元素数量的不同,使得排序过程中涉及的存储器不同,可以将排序方法分为两类。
一类是整个排序过程在内存储器中进行,称为内部排序;另一类是由于待排序元素数量太大,
以至于内存储器无法容纳全部数据,排序需要借助外部存储设备才能完成,这类排序称为外部排序。
本文涉及的排序均为内部排序。
四、排序结果是否唯一
如果是按照主关键字来排序,则得到的排序结果是唯一的。否则,如果按照次关键字,则得到的结果是不唯一的。
五、排序是否稳定
如果在待排序的序列中存在多个具有相同关键字的元素。假设元素 Ri 在 Rj 之前,两者关键字
相同,如果排序过后 Ri 仍在 Rj 前面,则称排序方法是稳定的,否则,当相同关键字元素的
前后关系在排序中发生变化,则称排序方法是不稳定的。无论是稳定的还是不稳定的排序方法,
均能完成排序的功能。在某些场合可能对排序有稳定性的要求,此时就应当选择稳定的排序方法。
六、排序的分类
按照排序过程中依据的原则对内部排序进行分类,大致可以分为插入排序、交换排序、选择排序、
归并排序等。
插入类排序:
基本思想:逐个考察每个待排序元素,将每一个新元素插入到前面已经排好序的序列中适当的位置上,
使得新序列仍然是一个有序序列。
类别:直接插入排序、折半插入排序、希尔排序。
交换类排序主要是通过两两比较待排元素的关键字,若发现与排序要求相逆,则“交换”
之。在这类排序方法中最常见的是起泡排序和快速排序,其中快速排序是一种在实际应用中
具有很好表现的算法。
选择排序的基本思想是:每一趟从n-i+1 (i=1,2,…,n)个元素中选取一个关键字最小的元
素作为有序序列中第i 个元素。本节在介绍简单选择排序的基础上,给出了对其进行改进的
算法——树型选择排序和堆排序。
七、基于比较的排序的对比
插入排序、交换排序、选择排序、归并排序等排序方法,都有一个共同的特点,那就是它们都是通过
比较元素的大小来确定元素之间的相对位置的,都是基于比较的排序方法。
从算法的平均时间复杂度、最坏时间复杂度、空间复杂度以及排序的稳定性等方面,对各种排序方法加以比较。
排序方法 平均时间复杂度 最坏时间复杂度 最好时间复杂度 空间复杂度 稳定性 复杂性 直接插入排序 O(n2 ) O(n2 ) O(n) O(1) 稳定 简单希尔排序 O(nlog2n ) O(nlog2n ) O(1) 不稳定 较复杂冒泡排序 O(n2 ) O(n2 ) O(n) O(1) 稳定 简单快速排序 O(nlog2n ) O(n2 ) O(nlog2n ) O(nlog2n ) 不稳定 较复杂直接选择排序 O(n2 ) O(n2 ) O(n2 ) O(1) 不稳定 简单堆排序 O(nlog2n ) O(nlog2n ) O(nlog2n ) O(1) 不稳定 较复杂归并排序 O(nlog2n ) O(nlog2n ) O(nlog2n ) O(n) 稳定 较复杂基数排序 O(d(n+r)) O(d(n+r)) O(d(n+r)) O(n+r) 稳定 较复杂 通过对排序可能出现的结果个数的研究,可以得出如下结论:
任何一个基于比较操作的排序方法,在最坏情况下所需要进行的比较次数至少为 nlogn 次,
即算法的时间复杂度下界为 Ω(nlogn)。
详细介绍
直接插入排序(简单插入排序)代码
基本思想:在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排好顺序的,
现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。
如此反复循环,直到全部排好顺序。
public static void insertSort(int[] r, int low, int high){ for(int tmp, j, i = low + 1; i <= high; i++){ if(r[i] < r[i-1]){ tmp = r[i]; j = i - 1; for(; j >= low && tmp < r[j]; j--) r[j+1] = r[j]; r[j+1] = tmp; } }}
希尔排序(最小增量排序)
基本思想:算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,
每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的
增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,
进行直接插入排序后,排序完成。
//method onepublic static void shellSort(int[] r, int low, int high){ for(int gap = r.length/2; gap > 0; gap /= 2){ for(int m = 0; m < gap; m++){ for(int tmp, j, i = low + gap; i <= high; i += gap){ if(r[i] < r[i-gap]){ tmp = r[i]; j = i - gap; for(; j >= low && tmp < r[j]; j -= gap) r[j+gap] = r[j]; r[j+gap] = tmp; } } } } }
//method twopublic static void shellSortII(int[] r, int low, int high){ for(int gap = r.length/2; gap > 0; gap /= 2){ for(int tmp, j, i = gap; i <= high; i++){ if(r[i] < r[i-gap]){ tmp = r[i]; j = i - gap; for(; j >= low && tmp < r[j]; j -= gap) r[j+gap] = r[j]; r[j+gap] = tmp; } } }}
//method threepublic static void shellSortIII(int[] r, int low, int high){ for(int gap = r.length/2; gap > 0; gap /= 2){ for(int i = gap; i <= high; i++){ for(int j = i-gap; j >= 0 && r[j] > r[j+gap]; j -= gap) swap(r, j, j+gap); } }}private static void swap(int[] r, int i, int j){ int tmp = r[i]; r[i] = r[j]; r[j] = tmp;}
冒泡排序代码
基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,
自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,
较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
public static void bubbleSort(int[] r, int low, int high){ for(int i = low; i <= high; i++){ for(int j = i+1; j <= high; j++){ if(r[i] > r[j]){ swap(r, i, j); } } }}private static void swap(int[] r, int i, int j){ int tmp = r[i]; r[i] = r[j]; r[j] = tmp;}
快速排序代码
基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,
将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,
此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
public static void quickSort(int[] r, int low, int high) { if (low < high) { int pa = partition(r, low, high); quickSort(r, low, pa - 1); quickSort(r, pa + 1, high); }}private static int partition(int[] r, int low, int high) { int pivot = r[low]; // 使用r[low]作为枢轴元素 while (low < high) { // 从两端交替向内扫描 while (low < high && r[high] > pivot) high--; r[low] = r[high]; // 将比pivot 小的元素移向低端 while (low < high && r[low] < pivot) low++; r[high] = r[low]; // 将比pivot 大的元素移向高端 } r[low] = pivot; // 设置枢轴 return low; // 返回枢轴元素位置}
直接选择排序(简单选择排序)
基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;
然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
public static void selectSort(int r[], int low, int high){ for(int i = low; i <= high; i++){ int min = i; for(int j = i+1; j <= high; j++){ if(r[j] < r[min]){ min = j; } } swap(r, i, min); }}private static void swap(int[] r, int i, int j){ int tmp = r[i]; r[i] = r[j]; r[j] = tmp;}
树形选择排序(锦标赛排序)
基本思想: 先把待排序的 n 个元素两两进行比较,取出较小者,若轮空则直接进入下一轮比较;
然后在[n/2]个较小者中,采用同样的方法进行比较,再选出较小者;如此反复,直到选出关键字最小的元素为止。
这个过程可以使用一颗具有 n 个结点的完全二叉树来表示,最终选出的关键字最小的元素就是这棵二叉树的根结点。
在输出关键字最小的元素后,为选出次小关键字,可以将最小关键字元素所对应的叶子结点的关键字设置为∞,
然后从该叶子结点起逆行向上,将所经过的结点与其兄弟进行比较,修改从该叶子结点到根结点上各结点的值,则根结点的值即为次小关键字。
public static void treeSelectSort(int[] r, int low, int high){ int len = high - low + 1; int treeSize = 2 * len - 1; int[] tree = new int[treeSize]; //fill leaf nodes for(int i=len-1, j=0; i >= 0; i--, j++){ tree[treeSize-1-j] = r[i]; } //fill non-leaf nodes for(int i=treeSize-1; i > 0; i -= 2){ tree[(i-1)/2] = tree[i-1] < tree[i] ? tree[i-1] : tree[i]; } int minIndex; while(low <= high){ int min = tree[0]; r[low++] = min; minIndex = treeSize - 1; while(tree[minIndex] != min) minIndex--; tree[minIndex] = Integer.MAX_VALUE; while(minIndex > 0){ //if it has parent node if(minIndex % 2 == 0){ //if it's the right node tree[(minIndex-1)/2] = tree[minIndex-1] < tree[minIndex] ? tree[minIndex-1] : tree[minIndex]; minIndex = (minIndex - 1)/2; } else { //if it's the left node tree[minIndex/2] = tree[minIndex] < tree[minIndex+1] ? tree[minIndex] : tree[minIndex+1]; minIndex = minIndex/2; } } }}
堆排序
基本思想: 设有n 个元素,欲将其按关键字排序。可以首先将这n 个元素按关键字建成堆,将堆顶
元素输出,得到n 个元素中关键字最大(或最小)的元素。然后,再将剩下的n-1 个元素重
新建成堆,再输出堆顶元素,得到n 个元素中关键字次大(或次小)的元素。如此反复执行,
直到最后只剩一个元素,则可以得到一个有序序列,这个排序过程称之为堆排序。
public static void heapSort(int[] r, int low, int high){ for(int i=high/2; i >= 0; i--) heapAdjust(r, i, high); int tmp; for(int i=high; i >= 0; i--){ tmp = r[0]; r[0] = r[i]; r[i] = tmp; heapAdjust(r, 0, i-1); }}private static void heapAdjust(int[] r, int low, int high){ int tmp = r[low]; for(int j=2*low+1; j <= high; j *= 2){ if(j < high && r[j] < r[j+1]) j++; if(tmp >= r[j]) break; r[low] = r[j]; low = j; } r[low] = tmp;}
归并排序
基本思想: 归并排序是建立在归并操作上的一种有效的排序算法,是采用分治法(divide and conquer)的一个
典型应用。先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
public static void mergeSort(int[] r, int low, int high){ if(low < high){ int center = (low+high)/2; mergeSort(r, low, center); mergeSort(r, center+1, high); merge(r, low, center, high); }}private static void merge(int[] r, int left, int center, int right){ int[] tmpArr = new int[right-left+1]; int middle = center + 1; int start = 0; int index = left; while(left <= center && middle <= right){ if(r[left] <= r[middle]){ tmpArr[start++] = r[left++]; }else { tmpArr[start++] = r[middle++]; } } while(left <= center) tmpArr[start++] = r[left++]; while(middle <= right) tmpArr[start++] = r[middle++]; for(int i=0; i < tmpArr.length; i++) r[index+i] = tmpArr[i];}//归并排序自底向上的方式/*1 xx|xx|xx|xx|xx|xx|xx|xx| 2 xxxx|xxxx|xxxx|xxxx| 3 xxxxxxxx|xxxxxxxx */public static void bottomUpMergeSort(int[] r, int low, int high){ int size = high - low + 1; //外循环为每次归并排序,每组数据的宽度,每组数据的宽度之后进行2倍递增 for(int width=1; width < size; width *= 2){ //内循环为基于每组数据的宽度,进行多组数据的归并排序 //index += 2 * width 因为一次归并排序都是使用 2 组数据进行排序, //所以每次递增两组数据的偏移量 //index < (size - width) 表示排序至少需要一组多的数据 for(int index=0; index < (size-width); index += 2*width){ int lo = index; int hi = index + (2 * width - 1); int mid = index + (hi - lo)/2; merge(r, lo, mid, hi); } }}
计数排序
基本思想: 计数排序是一种类似桶排序的算法,时间复杂度 O(n),其优势是对已知数量范围
的数组进行排序。它创建一个长度为这个数据范围的数组,这个数组的每个元素记录排序数组
中对应记录出现的个数。
//方案一,来自麻省的教材public static void countingSort(int[] in, int[] out, int k){ int[] temp = new int[k+1]; for(int i=0; i < in.length; i++) temp[in[i]]++; for(int i=1; i <= k; i++) temp[i] += temp[i-1]; for(int i=in.length-1; i >= 0; i--){ out[temp[in[i]]-1] = in[i]; temp[in[i]]--; }}//方案二public static void countingSortII(int[] r, int k){ int[] temp = new int[k+1]; for(int i=0; i < r.length; i++) temp[r[i]]++; for(int z=0, i=0; i <= k; i++){ while(temp[i]-- > 0) r[z++] = i; }}
桶排序(箱排序)
基本思想: 设待排序序列的元素取值范围为 0 到 m ,则我们新建一个大小为 m+1 的临时数组并把初始值都设为0,
遍历待排序序列,把待排序序列中元素的值作为临时数组的下标,找出临时数组中对应该下标的元素使之加1;
然后遍历临时数组,把临时数组中元素大于 0 的下标作为值按次序依次填入待排序数组,元素的值作为重复填入该下标的次数,
遍历完成则排序结束序列有序。
public static void bucketSort(int[] r, int max){ int[] tmp = new int[max+1]; for(int i=0; i < r.length; i++) tmp[r[i]]++; for(int i=0, j=0; i <= max; i++) for(int k=0; k < tmp[i]; k++) r[j++] = i;}
基数排序
基本思想: 基数排序不需要比较关键字的大小,它是根据关键字中各位的值,通过对排序的 n 个元素进行
若干趟“分配”与“收集”来实现排序的。
//获取指定位上的数字private static int getDigit(int x, int d){ int value = 1; for(int i=1; i < d; i++) value *= 10; return (x/value) % 10;}public static void radixSort(int[] r, int low, int high, int digit){ final int radix = 10; //基数 int[] count = new int[radix]; //存放各个桶数据的统计个数 int[] bucket = new int[high-low+1]; //桶 //从低位到高位排序 for(int i, j, d=1; d <= digit; d++){ //置空各个桶的数据统计 for(i=0; i < radix; i++) count[i] = 0; //统计各个桶的数据个数 for(i=low; i <= high; i++){ j = getDigit(r[i], d); count[j]++; } //累计小于桶数据 i 的数据个数 for(i=1; i < radix; i++) count[i] = count[i] + count[i-1]; //从右向左装桶,保证数据稳定性 for(i=high; i >= low; i--){ j = getDigit(r[i], d); bucket[count[j]-1] = r[i]; count[j]--; } //倒出桶中的数据 for(i=low, j=0; i <= high; i++, j++) r[i] = bucket[j]; }}
查看原文:http://localhost:8080/?p=121
- 内部排序算法比较
- 内部排序算法比较
- 内部排序算法
- 内部排序算法分析
- 各种内部排序算法!
- 内部排序算法比较
- 内部排序算法比较
- 内部排序算法比较
- 内部排序算法小结
- 内部排序算法练习
- 内部排序算法概述
- 内部排序算法小结
- 内部排序算法
- 内部排序算法总结
- 内部排序算法
- 内部排序算法
- c++内部排序算法
- 内部排序算法
- Component Object Model
- 研一篮球(2017赛季)
- word如何在任意页开始添加页码
- HDU-2042 不容易系列之二
- TensorFlow教程01:MNIST实验——MNIST介绍
- 内部排序算法
- 从高考到程序员--------你到底在追求着什么?------串烧似的文章
- ES2016 & ES2017
- 第四届“图灵杯”NEUQ-ACM程序设计竞赛(团队赛)-网络同步赛D 分形
- Linux下C高手成长过程----经典书籍推荐
- angualrjs 学习笔记初篇
- c++ 学习笔记之复合数据类型
- php运行原理如何理解
- c++ 学习笔记初篇