十大排序
秋招准备笔试,发现排序很不熟练,特意花一下午复习一下排序。
排序分为两种,内部排序和外部排序。
- 内排序:待排序序列完全存放在内存中进行的排序过程,这种方法适合数量不太大的数据元素的排序。
- 外排序:待排序元素非常多,以至于必须存放在外部存储器中,这种排序过程需要访问外存储器。
内排序都是一个逐步扩大记录的有序序列长度的过程。内部排序方法大致可分为:
- 插入类:将无序子序列中的一个或几个记录插入到有序序列中,从而增加记录的有序子序列的长度。
- 交换类:通过交换无序序列中的记录,从而得到其中关键字最小或最大的记录,并将它加入有序子序列中。
- 选择类:从记录的无序子序列选择关键字最小或最大的记录,并将它们加入有序子序列中。
- 归并类:通过归并两个或两个以上的记录有序子序列,逐步增加记录有序序列的长度。
- 其他类
插入排序
直接插入排序
基本思想:每趟将一条待排序的记录,按其关键字的大小插入到前面已排好序的记录序列中的适当位置,知道全部记录插入完成为止。
基本要求:假设待排序的记录存放在数组中,开始时将第0个记录组成一个有序的子表,然后依次将后面的记录插入到这个子表中,并且一致保持子表的有序性。
- 不带监视哨的直接插入排序:
public static int[] StraightInsertionSort(int []str){
int i , j;
j = 0;
for(i = 1 ; i < str.length ; i ++){
int temp = str[i];
for(j = i-1 ; j>=0 && temp<str[j] ;j--){
str[j+1] = str[j];
}
str[j+1] = temp;
}
return str;
}
结果:
- 带监视哨的直接插入排序:
将str[0]作为监视哨,在不带监视哨的插入排序中,判断条件比较复杂:j>=0 && temp<str[j]
改进:将temp的值存放进str[0],这样,当扫描至str[0]时,二值相等,自然就不满足循环条件,因此结束循环,这样就不需要判断下标是否越界了。
带监视哨的插入排序有一个问题:因为将str[0]作为监视哨来优化效率,因此str[0]中是不存放有效记录的,长度n的顺序表中实际上只能存储n-1个记录。
//带监视哨的直接插入排序
public static int[] SISwithGuard(int[] str){
int i , j ;
for(i = 2 ; i < str.length ; i++){
int temp = str[i];
str[0] = temp;
for(j = i -1 ; temp < str[j] ; j-- ){
str[j+1] = str[j];
}
str[j+1] = temp;
}
return str;
}
结果:str[0]为52,因为str[0]作为监视哨,所以那个52没参与排序。
性能分析
空间复杂度:O(1)
时间复杂度:O(n²)
希尔排序
基本思想:先选取一个小于n的整数d(增量),然后把排序表中的n个记录分为d个子表,从下表为0的记录开始,间隔为d的记录组成一个子表,在各个子表内进行直接插入排序,在一趟之后,间隔为d的记录组成的子表已经有序,随着有序性的改善,逐步减少d,直到d==1。
public void shellsort(int[] d) {//d为增量数组
RecordNode temp;
int i, j;
for (int k = 0; k < d.length; k++) {
int dk = d[k];
for (i = dk; i < this.curlen; i++) {
temp = r[i];
for (j = i - dk; j >= 0 && temp.key.compareTo(r[j].key) < 0; j -= dk) {
r[j + dk] = r[j];
}
r[j + dk] = temp;
}
}
}
- 希尔排序最后一次增量一定是1
- 分割后子序列内部排序算法为直接插入排序
交换排序
冒泡排序
基本思想:将待排序数组看成从上到下排放,把关键字值较小的记录看成“较轻的”,关键字值较大的看成“较重的”。较小关键字的记录就像气泡,向上浮,较大关键字值的记录向下。
注意点:需要一个flag变量来判断此轮遍历是否有交换,如果没有交换直接break,结束排序。
public static int[] BubbleSort(int[] str){
int i ,j;
boolean flag = true;
for(i = 1 ; i < str.length && flag ; i ++ ){
flag = false;
for(j = 0 ; j < str.length-i ; j++){
if(str[j+1] > str[j]){
int temp;
temp = str[j];
str[j] = str[j+1];
str[j+1] = temp;
flag = true;
}
}
}
return str;
}
快速排序
分治策略:将原问题划分成若干个规模较小但与原问题相似的子问题,然后再递归。
基本思想:通过一趟排序将要排序的记录分割成独立的两个部分,其中一个部分的所有记录的关键字值都比另外一部分的所有记录关键字小,然后再按此方法对两部分记录分别进行快速排序,整个排序过程递归进行。
public static int Partition(int i,int j){
RecordNode pivot = r[i];
while(i<j){//从表的两端交替的向中间扫描
while(i<j && pivot.key.compareTo(r[j].key) <= 0){
j--;
}
if(i<j){
r[i]=r[j];
i++;
}
while (i<j && pivot.key.compareTo(r[i].key)>0){
i++;
}
if(i<j){
r[j] = r[i];
j--;
}
}
r[i] = pivot;
return i;
}
public static void qSort(int low,int high){
if(low<high){
int pivotloc = Partition(low,high);
qSort(low,pivotloc-1);
qSort(pivotloc+1,high);
}
}
public static void quicksort(){
qSort(0,this.curlen);
}
选择排序
主要思想:每一趟从待排序列中选取一个关键字值最小的记录。
直接选择排序
直接选择排序(Straight Selection Sort)的基本思想是:在第1趟中,从n个记录中找出关键字值最小的记录与第1个记录交换;在第2趟中,从第2个记录开始的n-1个记录中再选出关键字值最小的记录与第二个记录交换;以此类推,在第i趟中,从第i个记录开始的n-i+1个记录中选出关键字值最小的记录与第i个记录交换,直到整个序列按关键字值有序为止。
public static int[] StraightSelect(int[] str){
int i,j;
for(i = 0 ; i < str.length -1; i ++){
int min = i;
for(j = i+1 ; j < str.length ; j++){
if(str[j] < str[min]){
min = j ;
}
}
if(min != i){
int temp = str[i];
str[i] = str[min];
str[min] = temp;
}
}
return str;
树形选择排序
(有点像带备忘录的递归)
堆排序
堆排序
升序用大根堆,降序就用小根堆
构建堆:
主要思路:第一次保证00位置大根堆结构(废话),第二次保证01位置大根堆结构,第三次保证02位置大根堆结构…直到保证0n-1位置大根堆结构(每次新插入的数据都与其父结点进行比较,如果插入的数比父结点大,则与父结点交换,否则一直向上交换,直到小于等于父结点,或者来到了顶端)
归并排序
归并:将两个或两个以上的有序表合并成一个新的有序表。其中,将两个有序表合并成一个有序表的归并排序称为二路归并排序,否则称为多路归并排序。
二路归并
核心操作:将两个相邻的有序序列归并成一个有序序列。