更好的记住排序算法,特做一下总结:
排序算法分为两类:内排序和外排序。内排序是在排序过程中,全部记录存放在内存中。外排序是在排序过程中需要使用外存。
内排序可以分为以下几类:
(1) 插入排序:直接插入排序,二分法插入排序,希尔排序
(2) 选择排序:简单选择排序,堆排序
(3) 交换排序:冒泡排序,快速排序
(4) 归并排序
(5) 基数排序
1、插入排序--直接插入排序
基本思想:每一步将一个带排序的记录,按其顺序码大小插入到前面已经排序的子序列的合适位置(从后往前找合适的位置后),直到全部插入排序为止。
如果碰见一个和插入记录相等的,那么把插入记录放在相等记录的后面。所以相当记录的先后顺序没有改变,所以简单插入排序是稳定的。
初态不同时,直接插入排序所耗费的时间有很大差异。若初态为正序,则每个待插入的记录只需要比较一次就能够找到合适的位置插入,故算法的时间复杂度为O(n),这时最好的情况。若初态为反序,则第i个待插入记录需要比较i+1次才能找到合适位置插入,故时间复杂度为O(n^2),这时最坏的情况。
直接插入排序的平均时间复杂度为O(n^2)。
Java代码实现:
public static void insertSort(int x[]){
int tmp;
for(int i=1;i<x.length;i++){
for(int j=i;j>0;j--){
if(x[j-1]>x[j]){
tmp = x[j];
x[j] = x[j-1];
x[j-1] = tmp;
}
}
for(int a=0;a<x.length;a++){
System.out.print(x[a]+" ");
}
System.out.println();
}
}
注:自己写的java代码,请指正。
2、插入排序--二分法插入排序
基本思想:二分法插入排序的思想和直接插入排序类似,都是找合适的位置把记录插入。不同的是找合适的插入位置不同,二分法插入排序是用二分法查找插入位置。
java代码实现:
public static void dichInsertSort(int x[]){
int low;
int high;
int mid;
for(int i=1;i<x.length;i++){
low=0;
high=i-1;
mid=0;
while(low<=high){
mid=(low+high)/2;
if(x[i]<x[mid]){
high=mid-1;
}else{
low=mid+1;
}
}
int tmp=x[i];
for(int j=i;j>low;j--){
x[j]=x[j-1];
}
x[low]=tmp;
for(int a=0;a<x.length;a++){
System.out.print(x[a]+" ");
}
System.out.println();
}
}
注:自己写的java代码,请指正。
二分法插入排序的比较次数与待排序记录的初始状态无关,仅依赖于记录的个数。当n较大时,比直接插入排序的最大比较次数少得多。但大于直接插入排序的最小比较次数。平均时间复杂度为O(n^2)。
3、插入排序--希尔排序
希尔排序,也称为递减增量排序算法,是直接插入排序算法的一种更高效的改进版本。希尔排序是不稳定的。
基本思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
java代码实现:
public static void shellSort(int x[]){
int tmp;
for(int d=x.length/2;d>=1;d--){
for(int i=0;i+d<x.length;i++){
if(x[i]>x[i+d]){
tmp = x[i+d];
x[i+d]=x[i];
x[i]=tmp;
}
}
for(int a=0;a<x.length;a++){
System.out.print(x[a]+" ");
}
System.out.println();
}
}
希尔排序的时间性能优于直接插入排序,原因如下:
4、选择排序--简单选择排序
基本思想:每趟从待排序的记录中选出关键字最小的记录,顺序放在已排好的序列的末尾,直到全部排序结束为止。简单选择排序不是稳定的。
处理过程:
(1)从待排序序列中,找到关键字最小的元素
(2)如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换
(3)重复( 1 )、( 2 )步,直到排序结束
java代码实现:
public static void simpleSelectSort(int x[]){
int tmp;
int max;
for(int i=0;i<x.length;i++){
tmp=x[i];
max=i;
for(int j=i;j<x.length;j++){
if(tmp<x[j]){
tmp=x[j];
max=j;
}
}
if(i!=max){
tmp=x[i];
x[i]=x[max];
x[max]=tmp;
}
for(int a=0;a<x.length;a++){
System.out.print(x[a]+" ");
}
System.out.println();
}
}
简单选择排序平均时间复杂度为:O(n^2)
5、选择排序--堆排序
堆的定义:具有n个元素的序列(k1,k2,...,kn), 且仅当满足,Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&Key[i]>=key[2i+2], 即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。
基本思想:初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。
实现堆排序需解决两个问题:
(1). 如何将n 个待排序的数建成堆;
(2). 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。
建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。
(1)n 个结点的完全二叉树,则最后一个结点是第个结点的子树。
(2)筛选从第个结点为根的子树开始,该子树成为堆。
(3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。
调整大顶堆的方法:
(1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。
(2)将根结点与左、右子树中较大元素的进行交换。
(3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).
(4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).
(5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。
java代码实现:public class HeapSort{
public static void main(String[]args){
System.out.println("Hello,World!");
int[] a={49,38,65,27,76,43,97,23};
printAr(a);
heapSort(a);
printAr(a);
}
public static void printAr(int arr[]){
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
System.out.println();
}
public static void swap(int arr[],int a,int b){
int tmp=arr[a];
arr[a]=arr[b];
arr[b]=tmp;
}
public static void heapSort(int arr[]){
buildMaxHeap(arr);
for(int i=arr.length-1;i>=1;i--){
swap(arr,i,0);
maxHeap(arr,0,i);
}
}
public static void buildMaxHeap(int arr[]){
int half=(arr.length-1)/2;
for(int i=half;i>=0;i--){
maxHeap(arr,i,arr.length);
}
}
public static void maxHeap(int arr[],int index,int size){
int left=index*2+1;
int right=index*2+2;
int max=index;
if(left<size&&arr[left]>arr[max]){
max=left;
}
if(right<size&&arr[right]>arr[max]){
max=right;
}
if(index!=max){
swap(arr,index,max);
maxHeap(arr,max,size);
}
}
}
堆排序的时间复杂度是:O(nlgn), 堆排序是不稳定的
6、交换排序--冒泡排序
基本思想:临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换,这样一趟过去之后,最大或者最小的数被交换到了最后一位。
java代码实现:
public static void bubbleSort(int[] arr){
int tmp = 0;
for(int i = arr.length-1;i>=0;i--){
for(int j = 0;j < i;j++){
if(arr[j] > arr[j+1]){
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
for(int a=0;a<arr.length;a++){
System.out.print(arr[a]+" ");
}
System.out.println();
}
}
冒泡排序的平均时间复杂度为:O(n^2),冒泡排序是一种稳定的排序算法。
7、交换排序--快速排序
基本思想:通过一躺排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
快速排序是一种不稳定的排序算法
java代码实现:
public static void quickSort(int[] arr,int low,int high){
if(low<high){
int i = low;
int j = high;
int key = arr[low];
while(i<j){
while(i<j&&arr[j]>key) j--;
arr[i] = arr[j];
while(i<j&&arr[i]<key) i++;
arr[j] = arr[i];
}
arr[i] = key;
quickSort(arr,low,i-1);
quickSort(arr,i+1,high);
}
}
8、交换排序--归并排序
基本思想:归并排序是建立在归并操作上的一种有效的排序算法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
归并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。
归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的额外空间,时间复杂度为O(nlog(n))
java代码实现:
public class MergeSort{
public static void main(String[]args){
System.out.println("Hello,World!");
int[] a={49,38,65,27,76,43,97,23};
printAr(a);
sort(a, 0, a.length-1);
printAr(a);
}
public static void printAr(int arr[]){
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
System.out.println();
}
public static void merge(int[] arr, int low, int mid, int high) {
int[] temp = new int[high - low + 1];
int i = low;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= high) {
if (arr[i] < arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= high) {
temp[k++] = arr[j++];
}
for (int k2 = 0; k2 < temp.length; k2++) {
arr[k2 + low] = temp[k2];
}
}
public static void sort(int[] arr, int low, int high) {
int mid = (low + high) / 2;
if (low < high) {
sort(arr, low, mid);
sort(arr, mid + 1, high);
merge(arr, low, mid, high);
}
}
}
9、交换排序--基数排序
基本思想:基数排序必须依赖于另外的排序方法。基数排序的总体思路就是将待排序数据拆分成多个关键字进行排序,也就是说,基数排序的实质是多关键字排序。
多关键字排序时有两种解决方案:
最高位优先法(MSD)(Most Significant Digit first)
最低位优先法(LSD)(Least Significant Digit first)
以LSD为例:
第一步
假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39第二步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93第三步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。
java代码实现:
LSD方法:(参考http://blog.csdn.net/skylinesky/article/details/6612119)