八大排序:
插入排序(直接插入排序、希尔排序)
选择排序(简单选择排序、堆排序)
交焕排序(冒泡排序、快速排序)
归并排序
基数排序
当n较大,则应采用时间复杂度为O(nlogn)的排序方法:快速排序、堆排序、归并排序。
快速排序:是目前基于比较的内部排序中被认为最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短。
1.插入排序——直接插入排序
基本思想:将一个记录插入到已排序好的有序表中,从而得到一个新、记录数增1的有序表。即:先将序列的第一个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
要点:设立哨兵,作为临时存储和判断数组边界之用。
如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素之后。所以,相等元素的前后顺序没有改变,从原无序序列出去的舒徐就是排好序后的顺序,所以插入排序是稳定的。
算法实现:
static void InsertSort(int a[], int n) {
int i, j;
for (i = 1; i < n; i++) {
if (a[i] < a[i - 1]) {
int temp = a[i];
for (j = i - 1; j >= 0 && a[j] > temp; j--)
a[j + 1] = a[j];
a[j + 1] = temp;
for (int t : a) {
System.out.print(t + " ");
}
System.out.println();
}
}
}
public static void main(String[] args) {
int[] a = new int[] { 3, 1, 5, 7, 2, 4, 9, 6 };
InsertSort(a, 8);
}
效率:
时间复杂度:O(n^2)。
2.插入排序——希尔排序
希尔排序是1959年由D.L.Shell提出来的,相对直接排序有较大的改进。希尔排序又称缩小增量排序
基本思想:
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
操作方法:
——选择一个增量序列t1、t2、t3、....tk,其中ti>tj,tk=1;
——按增量序列个数k,对序列进行k趟排序;
——每趟排序,根据对应的增量ti,将待排序序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序。仅增量因子为1时,整个序列作为一个表来处理,表长度即为整个序列的长度。
算法实现:
我们简单处理增量序列:增量序列d={n/2,n/4,n/8........1}n为要排序数的个数。
即:先将要排序的一组记录按某个增量d(n/2,n为要排序数的个数)分成若干组子序列,每组记录的下标相差d,对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对他进行分组,在每组中在进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。
static void ShellInsertSort(int a[]) {
// 增量i,并逐步缩小增量
for (int i = a.length / 2; i > 0; i /= 2) {
for (int j = i; j < a.length; j++) {
int k = j;
while (k - i >= 0 && a[k] < a[k - i]) {
// 插入排序采用交换法
swap(a, k, k - i);
k -= i;
}
}
for (int t : a) {
System.out.print(t);
}
System.out.println();
}
}
static void swap(int arr[], int a, int b) {
arr[a] = arr[a] + arr[b];
arr[b] = arr[a] - arr[b];
arr[a] = arr[a] - arr[b];
}
public static void main(String[] args) {
int[] arr = { 1, 4, 2, 7, 9, 8, 3, 6 };
ShellInsertSort(arr);
}
希尔排序时效分析很难,关键码的比较次数与记录移动次数依赖于增量因子序列d的选取,特定情况下可以精准估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的增量因子序列的方法。增量因子序列可以有各种取法,有取奇数的,也有取质数的,但需要注意:增量因子中除1外没有公因子,且最后一个增量因子必须为1。希尔排序方法是一个不稳定的排序方法。
3.选择排序——简单选择排序
基本思想:
在要排序的一组数中,选出最小(或者最大)的一个数与第一个位置的数交焕,然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交焕,依次类推,知道第n-1个元素(倒数第2个数)和第n个元素(最后一个数)比较为止。
操作方法:
——从n个记录中找出关键码最小的记录与第一个记录交焕
——从第2个记录开始的n-1个记录中再选出关键码最先的记录与第2个记录交焕
......
——从第i个记录开始的n-i+1个记录中选出关键码最小的记录与第i个记录交焕,直到整个序列按关键码有序。
算法实现:
static void SelectSort(int a[]) {
if (a == null || a.length <= 0) {
return;
}
for (int i = 0; i < a.length; i++) {
int temp = a[i];
int flag = i; // 将当前下标定义为最小值下标
for (int j = i + 1; j < a.length; j++) {
if (a[j] < temp) {// a[j] < temp 从小到大排序;a[j] > temp 从大到小排序
temp = a[j];
flag = j; // 如果有小于当前最小值的关键字将此关键字的下标赋值给flag
}
}
if (flag != i) {
a[flag] = a[i];
a[i] = temp;
}
}
for (int t : a) {
System.out.println(t);
}
}
public static void main(String[] args) {
int[] arr = { 1, 4, 2, 7, 9, 8, 3, 6 };
SelectSort(arr);
}
简单选择排序的改进——二元选择排序
简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需要(n/2)趟循环即可。
static void SelectSort2(int a[]) {
int n = a.length;
int i, j, max, min, temp;
for (i = 0; i < n / 2; i++) {
max = i;
min = i;
for (j = i + 1; j < n - 1; j++) {
if (a[j] < a[min]) {
min = j;
}
if (a[j] > a[max]) {
max = j;
}
}
temp = a[i];
a[i] = a[min];
a[min] = temp;
temp = a[n - i - 1];
a[n - i - 1] = a[max];
a[max] = temp;
}
for (int t : a) {
System.out.println(t);
}
}
4.选择排序——堆排序
堆排序是一种树形选择排序,是对直接选择排序的有效改进。
基本思想:
堆的定义如下:具有n个元素的序列(k1,k2,.....kn),当且仅当满足
时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小堆顶)。
若以一维数组存储一个堆,则堆对应一颗完全二叉树,且所有非叶节点的值均不大于(或不小于)其子女的值,根节点(堆顶元素)的值是最小(或最大)的。如:
(a)大顶堆序列:(96, 83,27,38,11,09)
(b) 小顶堆序列:(12,36,24,85,47,30,53,91)
初始时要把排序的n个数的序列看作是一颗顺序存储的二叉树(一维数组存储二叉树),调整他们的存储序列,使之成为一个堆,将堆顶元素输出,得到n个元素中最小(或最大)的元素,这是堆的根节点的数最小(或最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n个元素中次小(或次大)的元素。依次类推,直到只有两个节点的堆,并对他们做交换,最后得到有n个节点的有序序列。称这个过程为堆排序。
因此,实现堆排序需要解决两个问题:
——如何将n个待排序的数建成堆;
——输出堆顶元素后,怎样调整剩余n-1个元素,使其成为一个新堆。
首先谈论第二个问题,输出堆顶元素后,对剩余n-1个元素重新建成堆的调整过程。
调整小堆顶的方法:
——设有m个元素的堆,输出堆顶元素后,剩下m-1个元素。将堆底元素送入堆顶(最后一个元素与堆顶进行交焕),堆被破坏,其原因仅是根节点不满足堆的性质;
——将根节点与左、右子树中较小元素的进行交换;
——若与左子树交焕,如果左子树堆被破坏,即左子树的根节点不满足堆的性质,则重复方法第二步;
——若与右子树交焕,如果右子树堆被破坏,即右子树的根节点不满足堆的性质,则重复方法第二步;
——继续对不满足堆性质的子树进行上述交换操作,直到叶子节点,堆被建成。
称这个自根节点到叶子节点的调整过程为筛选。如图:
再讨论对n个元素初始建堆的过程。
建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。
——n个节点的完全二叉树,则最后一个节点是第(n/2)个节点的子树;
——筛选从第(n/2)个节点为根的子树开始,该子树成为堆;
——之后向前依次对各节点为根的子树进行筛选,使之成为堆,直到根节点。
如图建堆初始过程:无序序列:(49,38,65,97,76,13,27,49)
算法的实现:
从算法描述来看,堆排序需要两个过程,一个是建堆,另一个是堆顶与堆的最后一个元素交换位置。所有堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
static void heapSort(int arr[]) {
// 对数组进行建堆操作,就是从最后一个非叶子节点进行筛选的过程
for (int i = (arr.length - 1) / 2; i > 0; i--) {// 因为0没有使用,所以length-1
heapAdjust(arr, i, arr.length - 1);
}
System.out.println("建堆完成");
outPutArr(arr, 1);
for (int i = arr.length - 1; i > 1; i--) {
int temp = arr[1];
arr[1] = arr[i];
arr[i] = temp;
heapAdjust(arr, 1, i - 1);
}
}
static void heapAdjust(int arr[], int s, int m) {
// 已知arr[s...m]中记录的关键字除arr[s]之外均满足堆的定义,本函数调整arr[s]的关键字,使arr[s...m]成为一个最大堆
int rc = arr[s]; // s是最后一个非叶子节点
for (int j = 2 * s; j <= m; j = s * 2) {
if (j < m && arr[j] < arr[j + 1])
j++;
if (rc >= arr[j])
break;
arr[s] = arr[j]; // 上移到父节点
s = j;
}
arr[s] = rc; // 要放入的位置
}
static void outPutArr(int arr[], int i) {
if (i <= arr[0]) {
System.out.println(arr[i]);
outPutArr(arr, i * 2); // 左
outPutArr(arr, i * 2 + 1); // 右
}
}
public static void main(String[] args) {
int[] arr = { 7, 23, 45, 9, 40, 73, 12, 49 };
heapSort(arr);
for (int i : arr) {
System.out.print(i + " ");
}
}
分析:
设树深度为k,。从根到叶的筛选,元素比较次数至多2(k-1)次,交换记录至多k 次。所以,在建好堆后,排序过程中的筛选次数不超过下式:
而建堆时的比较次数不超过4n 次,因此堆排序最坏情况下,时间复杂度也为:O(nlogn )。
5.交换排序——冒泡排序
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较发现他们的排序与排序要求的相反时,就将他们互换。
算法实现:
static void bubbleSort(int a[]) {
for (int i = 0; i < a.length - 1; ++i) {
for (int j = 0; j < a.length - i - 1; ++j) {
if (a[j] > a[j + 1]) {
int tmp = a[j];
a[j] = a[j + 1];
a[j + 1] = tmp;
}
}
}
for (int t : a) {
System.out.println(t);
}
}
public static void main(String[] args) {
int[] a = new int[] { 3, 1, 5, 7, 2, 4, 9, 6 };
bubbleSort(a);
}
冒泡排序算法的改进:
对冒泡算法常见的改进方法是加入一标志性变量exchange,用于标识某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程,本文再提供两种改进算法:
——设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。
改进后的算法如下:
static void bubble_1(int a[]) {
int i = a.length - 1; // 初始时,最后位置保持不变
while (i > 0) {
int pos = 0; // 每趟开始时,无记录交换
for (int j = 0; j < i; j++) {
if (a[j] > a[j + 1]) {
pos = j; // 记录交换位置
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
i = pos; // 为下一趟排序做准备
}
for (int t : a) {
System.out.println(t);
}
}
——传统冒泡排序中每一趟排序只能找到一个最大值或最小值,我们考虑利用在每趟排序中进行正向和反向两边冒泡的方法一次可得到两个最终值(最大值和最小值),从而使排序趟数几乎减少了一半。
static void bubble_2(int a[]) {
int low = 0;
int high = a.length - 1; // 设置变量的初始值
int temp, j;
while (low < high) {
for (j = low; j < high; ++j) { // 正向冒泡,找到最大值
if (a[j] > a[j + 1]) {
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
--high; // 修改high值,前移一位
for (j = high; j > low; --j) { // 反向冒泡,找到最小值
if (a[j] < a[j - 1]) {
temp = a[j];
a[j] = a[j - 1];
a[j - 1] = temp;
}
}
++low; // 修改low值,后移一位
}
for (int t : a) {
System.out.println(t);
}
}
6.交换排序——快速排序
基本思想:
——选择一个基准元素,通常选择第一个元素或者最后一个元素;
——通过一趟排序,对待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小,另一部分记录的元素值比基准元素大;
——此时基准元素在其排好序后的正确位置;
——然后分别对这两部分用同样的方法继续进行排序,直到整个序列有序。
快速排序示例:
(a)一趟排序的过程:
(b)排序的全过程
static int partition(int array[], int low, int high) {
// 固定的切分方式
int key = array[low];
while (low < high) {
while (array[high] >= key && high > low) { // 从后半部分向前扫描
high--;
}
array[low] = array[high];
while (array[low] <= key && high > low) { // 从前半部分向后扫描
low++;
}
array[high] = array[low];
}
array[high] = key;
return high;
}
static void quickSort(int array[], int low, int high) {
if (low >= high) {
return;
}
int index = partition(array, low, high);
quickSort(array, low, index - 1);
quickSort(array, index + 1, high);
}
public static void main(String[] args) {
int[] a = new int[] { 3, 1, 5, 7, 2, 4, 9, 6 };
quickSort(a, 0, a.length-1);
for (int t : a) {
System.out.println(t);
}
}
分析:
快速排序是通常被认为在同数量级O(nlogn)的排序方法中平均性能最好的。但若初始序列按关键字有序或基本有序时,快速排序反而蜕化为冒泡排序。为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。
快速排序的改进:
在本改进算法中,只对长度大于k的子序列递归调用快速排序,让原序列基本有序,然后再对整个基本有序序列用插入排序算法排序。实践证明,改进的算法时间复杂度有所降低,且当k取值为8左右时,改进算法的性能最佳。
static int partition_1(int array[], int low, int high) {
// 三数取中
int mid = low + (high - low) / 2;
if (array[mid] > array[high]) {
swap(array[mid], array[high]);
}
if (array[low] > array[high]) {
swap(array[low], array[high]);
}
if (array[mid] > array[low]) {
swap(array[mid], array[low]);
}
int key = array[low];
while (low < high) {
while (array[high] >= key && high > low) {
high--;
}
array[low] = array[high];
while (array[low] <= key && high > low) {
low++;
}
array[high] = array[low];
}
array[high] = key;
return high;
}
static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
static void quickSort_1(int array[], int low, int high) {
if (low >= high) {
return;
}
int index = partition_1(array, low, high);
quickSort_1(array, low, index - 1);
quickSort_1(array, index + 1, high);
}
public static void main(String[] args) {
int[] a = new int[] { 3, 1, 5, 7, 2, 4, 9, 6 };
quickSort_1(a, 0, a.length-1);
for (int t : a) {
System.out.println(t);
}
}
7.归并排序
基本思想:
归并排序是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
归并排序示例:
合并方法:
设[i.....n]由两个有序子表a[i.....m]和b[m+1.....n]组成,两个子表长度分别为n-i+1、n-m。
——j=m+1;k=i;i=i;//置;两个子表的起始下标及辅助数组的起始下标
——若i>m或j>n,转第四步//其中一个子表已合并完,比较选取结束
——//选取a[i]和a[j]较小的存入辅助数组b,如果a[i]<a[j],b[k]=a[i];i++;k++;转第二步,否则,b[k]=a[j];j++;k++;转第二步
——//将尚未处理完的子表中元素存入b,如果i<=m,将a[i.....m]存入b[k.....n]//前一子表非空。如果j<=n,将[j.....n]存入b[k.....n]//后一子表非空
——合并结束。
static void mergeArray(int a[],int n,int b[],int m,int c[]){
int i,j,k;
i=j=k=0;
while(i<n&&j<m){
if(a[i]<b[j]){
c[k++]=a[i++];
}else{
c[k++]=b[j++];
}
}
//将数组a或数组b中剩余的元素加入数组中
while(i<n){
c[k++]=a[i++];
}
while(j<m){
c[k++]=b[j++];
}
}
上面的合并算法的效率还是比较高的,可以达到O(n)的时间复杂度。
上面已经解决了合并有序序列的问题,下面再来看看二路归并的方法,其的基本思想就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。可以将A,B组各自在分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。
//二路归并
static void mergeArray_1(int a[], int first, int mid, int last, int temp[]) {
int i = first, j = mid + 1; // 设置两个数组的起始边界
int m = mid, n = last; // 设置两个数组的结束边界
int k = 0;
while (i <= m && j <= n) {
if (a[i] <= a[j]) {
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
while (i <= m) {
temp[k++] = a[i++];
}
while (j <= n) {
temp[k++] = a[j++];
}
for (i = 0; i < k; i++) {
a[first + i] = temp[i];
}
}
static void mergeSort(int a[], int first, int last, int temp[]) {
if (first < last) {
int mid = (first + last) / 2;
mergeSort(a, first, mid, temp); // 左边有序
mergeSort(a, mid + 1, last, temp); // 右边有序
mergeArray_1(a, first, mid, last, temp); // 再将两个有序序列合并
}
}
public static void main(String[] args) {
int[] arr = { 1, 9, 3, 12, 7, 8, 3, 4, 65, 22 };
int[] temp = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
mergeSort(arr, 0, arr.length - 1, temp);
for (int i : arr) {
System.out.print(i + ",");
}
}
归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(n),故一共为O(nlogn)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(nlogn)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。
8.桶排序/基数排序
说基数排序之前,我们先说桶排序。
基本思想:是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳排序。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间O(n)。但桶排序并不是比较排序,他不受到O(nlogn)下限的影响。
简单来说,就是把数据分组,放在一个个的桶中,然后对每个桶里面的数据进行排序。
例如要对大小为[1..1000]范围内的n个整数A[1..n]排序 ,首先,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储 (10..20]的整数,……集合B[i]存储( (i-1)*10, i*10]的整数,i = 1,2,..100。总共有 100个桶。 然后,对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任 何排序法都可以。最后,依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这 样就得到所有数字排好序的一个序列了。 假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果 对每个桶中的数字采用快速排序,那么整个算法的复杂度是 O(n + m * n/m*log(n/m)) = O(n + nlogn - nlogm) 从上式看出,当m接近n的时候,桶排序复杂度接近O(n) 。当然,以上复杂度的计算是基于输入的n个数字是平均分布这个假设的。这个假设是很强的 ,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。 前面说的几大排序算法 ,大部分时间复杂度都是O(n^2),也有部分排序算法时间复杂度是O(nlogn)。而桶式排序却能实现O(n)的时间复杂度。但桶排序的缺点是:
——首先是空间复杂度比较高,需要的额外开销大。排序有两个数组的空间开销,一个存放待排序数组,一个就是所谓的桶,比如待排序值是从0到m-1,那就需要m个桶,这个桶数组就要至少m个空间。
——其次待排序的元素都要在一定的范围内等等。
——桶式排序是一种分配排序。分配排序的特定是不需要进行关键码的比较,但前提是要知道待排序列的一些具体情况。
分配排序的基本思想就是进行多次的桶排序。
基数排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。他们的时间复杂度可以达到线性阶O(n)。
两种多关键码排序方法:
多关键码排序按照从最主位关键码到最次位关键码或从最次位到最主位关键码的顺序逐次排序,分两种办法:
最高位优先,简称MSD法:
——先按k1排序分组,将序列分成若干子序列,同一组序列的记录中,关键码k1相等。
——再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,知道按最次位关键码kd对各子组排序后。
——再将各组连接起来,便得到一个有序序列。
最低为优先,简称LSD法:
——先从kd开始排序,再对kd-1进行排序,依次重复,直到按k1排序分组分成最小的子序列后。
——最后将各个子序列连接起来,便可得到一个有序的序列。
基于LSD方法的链式基数排序的基本思想:
“多关键字排序”的思想实现“单关键字排序”。对数字型或字符型的单关键字,可以看作由多个数位或多个字符构成的多关键字,此时可采用“分配-收集”的方法进行排序,这一过程称作基数排序法,其中每个数字或字符可能的取值个数称为基数。
基数排序:
是按照低位先排序,然后收集;再按照高位排序,然后收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,在按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
算法实现:
// pos=1表示个位,pos=2表示十位
static int getNumInPos(int num, int pos) {
int tmp = 1;
for (int i = 0; i < pos - 1; i++) {
tmp *= 10;
}
return (num / tmp) % 10;
}
// 求得最大位数d
static int getMaxWeishu(int[] a) {
int max = a[0];
for (int i = 0; i < a.length; i++) {
if (a[i] > max)
max = a[i];
}
int tmp = 1, d = 1;
while (true) {
tmp *= 10;
if (max / tmp != 0) {
d++;
} else {
break;
}
}
return d;
}
static void radixSort(int a[], int d) {
int array[][] = new int[10][a.length + 1];
for (int i = 0; i < 10; i++) {
array[i][0] = 0;
// array[i][0]记录第i行数据的个数
}
for (int pos = 1; pos <= d; pos++) {
for (int i = 0; i < a.length; i++) {
// 分配过程
int row = getNumInPos(a[i], pos);
int col = ++array[row][0];
array[row][col] = a[i];
}
for (int row = 0, i = 0; row < 10; row++) {
// 收集过程
for (int col = 1; col <= array[row][0]; col++) {
a[i++] = array[row][col];
}
array[row][0] = 0;
// 复位,下一个pos时还需使用
}
}
}
public static void main(String[] args) {
int[] a = { 49, 38, 65, 197, 76, 213, 27, 50 };
radixSort(a, getMaxWeishu(a));
for (int i : a)
System.out.print(i + " ");
}
总结:
各种排序的稳定性,时间复杂度和空间复杂度总结
我们比较时间复杂度函数的情况:
时间复杂度函数O(n)的增长情况
所以对n较大的排序记录。一般的选择都是时间复杂度为O(nlogn)的排序方法。
时间复杂度来说:
——平方阶O(n^2)排序
各类简单排序:直接插入、直接排序、冒泡排序;
——线性对数阶O(nlogn)排序
快速排序、堆排序、归并排序;
——O(n1+§))排序,§是介于0和1之间的常数。
希尔排序;
——线性阶O(n)排序
基数排序、桶排序、箱排序;
说明:
当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O(n);
而快速排序则相反,当原表基本有序时,将退化为冒泡排序,时间复杂度提高为O(n^2);
原表是否有序,对简单选择排序、堆排序、归并排序和基数排序时间复杂度影响不大。
稳定性:
若待排序的序列中,存在多个具有相同关键字的记录,经过排序,这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对次序发生了改变,则称该算法是不稳定的。
稳定的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较,稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序,不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
选择排序算法准则:
每种排序算法都各有优缺点。因此,在使用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。选择排序算法的依据:影响排序的因素很多,平均时间复杂度低的算法并不一定是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:
——待排序的记录数目n的大小;
——记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
——关键字的结构及其分布情况;
——对排序稳定性的要求。