插入排序和快速排序

 插入排序

 插入排序的基本思想是:每一趟将一个待排序的记录,按器关键字的大小插入到已经排好序的一组记录的适当位置上,直到所有待排序记录全部插入为止。

例如,打扑克牌在抓牌的时要保证抓过的牌有序排列则每抓一张牌,就插入到合适的位置,直到抓完牌为止,即可得到一个有序序列。

可以选择不同的方法在已排好序的记录中寻找插入位置。根据查找方法的不同,有多种插入排序方法,这里主要介绍直接插入排序。

直接插入排序

直接插入排序(Straight  Insertion  Sort)是一种最简单的排序方法,其基本操作是将一条记录插入到已排好序的有序表中,从而得到一个新的、记录数量增1的有序表

算法步骤

1)设待排序的记录放在数组中r[1...n]中,r[1]是一个有序序列。

2)循环n-1次,每次使用顺序查找法,查找r[i](i=2,...n)在已排好序的序列r[1...i-1]中的插入位置,然后将r[i]插入表长为i-1的有序序列r[1...i-1],直接将r[n]插入表长为n-1的有序序列r[1...n-1],最后得到一个表长为n的有序序列

插入排序示意图

java代码的实现

package com.hxy.sort;

import java.util.Arrays;

public class InsertSort {
    public static void main(String[] args) {
        int[] arr ={49,38,65,97,76,13,27,49};
        System.out.println("排序之前的数组");
        System.out.println(Arrays.toString(arr));
        insertSort(arr);
        System.out.println("排序之后的数组");
        System.out.println(Arrays.toString(arr));
    }
    public static void insertSort(int[] arr){
        for (int i = 1; i<arr.length;i++){
            if(arr[i]<arr[i-1]){//若第i个元素小于第i-1个元素,移动有序序列, 如果大于的话则直接插入
                int temp = arr[i];//存储待插入的记录
                arr[i] = arr[i-1];
                int j = i-1;
                for(;j>=0&&temp<arr[j];j--){//判断条件
                    arr[j+1] = arr[j];
                }
                arr[j+1] = temp;// 将待插入的元素插到指定位置
            }
        }
    }
}

结果如下图所示

 动画示意图

 插入排序

时间复杂度

对于整个排序过程需执行n-1趟,在最好的情况下(正序:待排序序列中记录按关键字非递减有序排列),总的移动比较次数达到最小值n-1,记录不需移动;最坏情况下(即待排序表为逆序),总的关键字比较次数为(n+2)(n-1)/2≈n^{2}/2,记录移动次数为(n+4)(n-1)/2≈n^{2}/2。若待排序序列中出现各种可能排列的概率相同,则可取上述最好情况和最坏情况的平均情况。在平均情况下,直接插入排序关键字的比较次数和记录移动次数均约为n^{2}/4。由此,直接插入排序的时间复杂度为O(n^{2}

空间复杂度

直接插入排序只需要一个记录的辅助空间temp,所以空间复杂度为O(1)。

算法的特点

(1)稳定排序

(2)算法简单,且容易实现

(3)也适用于链式存储结构,只是在单链表上无需移动记录,只需修改相应的指针。

(4)更适合初始记录基本有序(正序)的情况,当初始记录无序,n较大时,此算法的时间复杂度较高,不宜采用

快速排序

快速排序(Quick Sort)是由冒泡排序改进而得的。在冒泡排序过程中,只对相邻的两个记录进行比较,因此每次交换两个相邻的记录时只能消除一个逆序。如果能通过两个(不相邻)记录的一次交换,消除多个逆序,则会大大加快排序的速度。快速排序方法中的一次交换可能消除多个逆序。

 

算法步骤

在待排序的n个记录中任取一个记录(通常取第一个记录)作为枢轴(或支点),设其关键字为pivotkey。经过一趟排序后,把所有关键字小于pivotkey的记录交换到前面,把所有关键字大于pivotkey的记录交换到后面,结果将待排序记录分成两个子表,最后将枢轴放置在分界处的位置。然后,分别对左、右子表重复上述过程,直至每一个子表只有一个记录时,排序完成。

其中,一趟快速排序的具体步骤如下。

  1)选择待排序表中的第一个记录作为枢轴,将枢轴记录暂存在r[0]的位置上。附设两个指针low和high,初始时分别指向表的下界和上界(第一趟时,low=1;high=L.length)。

  2)从表的最右侧位置依次向左搜索,找到第一个关键字小于枢轴关键字pivotkey的记录,将其移到low处。具体操作是当low<high时,若high所指记录的关键字大于等于pivotkey,则向左移动指针high(执行操作high- -);否则将high所指记录与枢轴记录录交换。

  3)然后再从表的最左侧位置,依次向右搜索找到第一个关键字大于pivotkey的记录和枢轴记录交换。具体操作是:当low<high时,若low所指记录记录关键字小于等于pivotkey,则向右移动指针low(执行操作low++);否则将low所指记录与枢轴记录交换。

  4)重复步骤2)和3),直至low与high相等为止。此时low或high的位置即为枢轴在此趟排序中的最终位置,原表被分成两个子表。

  在上述过程中,记录的交换都是与枢轴之间发生,每次交换都要移动3次记录,可以先将枢轴记录暂存在r[0]的位置上,排序过程中只要移动与枢轴交换的记录,即只做r[low]或r[high]的单向移动,直至一趟排序结束后再将枢轴记录移至正确位置上 

java代码的实现

       

 
 
package com.hxy.sort;

import java.util.Arrays;

public class QuickSort {
public static void main(String[] args) {

int[] arr ={8,5,7,6,9,5,4,2,10};
System.out.println("排序之前的顺序");
System.out.println(Arrays.toString(arr));
//int [] arr = {4,6};
//int[] arr = {1,2,3,4,5,6,7,7,7,9};
quickSort(arr,0,arr.length-1);
System.out.println("排序之后的顺序");
System.out.println(Arrays.toString(arr));

}
public static void quickSort(int[] arr , int leftBound, int rightBound){
if(leftBound>=rightBound){
return;
}
int mid = partition(arr, leftBound, rightBound);
quickSort(arr,leftBound,mid-1);
quickSort(arr,mid+1,rightBound);
}
public static int partition(int[] arr,int leftBound,int rightBound){
int pivot = arr[rightBound];
int left = leftBound ;
int right = rightBound-1;
while (left<right){
while (arr[left]<=pivot && left<=right)left++;

while (arr[right]>pivot && left<right)right--;

if(left<right) {
swap(arr, left, right);
}
}
if(arr[left]>pivot)
swap(arr,left,rightBound);
return left;

}
static void swap(int[] arr, int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j]= temp;
}
}

 

结果如下图所示      

 

动画示意图

时间复杂度

从快速排序算法的递归树可知,快速排序的趟数取决于递归树的深度。

最好情况下:每一趟排序后都能将记录序列均匀地分割成两个长度大致相等的子表,类似折半查找。在n个元素的序列中,对枢轴定位所需时间为O(n).若设T(n)是对n个元素的序列进行排序所需的时间,而且每次对枢轴正确定位后,正好把序列划分为长度相等的两个子表,此时,设Cn是一个常数,表示n个元素进行一趟快速排序的时间,则总排序的时间为

T(n)=Cn+2T(n/2)

  ≤n+2T(n/2)

  ≤n+2(n/2+2T(n/4))=2n+4T(n/4)

  ≤2n+4(n/4+2T(n/8))=3n+8T(n/8)

  ……

  ≤kn+2kT(n/2k)

∵k=n

∴T(n) ≤nn+nT(1)≈O(nn)

  最坏情况下:在待排序序列已经排好序的情况下,其递归树成为单支树,每次划分只得到一个比上一次少一个记录的子序列。这样必须经过n-1趟才能将所有记录定位,而且第i趟需要经过n-i次比较。这样,总的关键字比较次数为n(n-1)/2≈n2/2

  这种情况下,快速排序的速度已经退化到简单排序的水平。枢轴记录的合理选择可避免这种最坏情况的出现,如利用“三者取中”的规则:比较当前表中第一个记录,最后一个记录和中间一个记录的关键字,取关键字居中的记录作为枢轴记录,事先调换到第一个记录的位置。

  理论上可以证明,平均情况下,快速排序的时间复杂度为O(nn)。

空间复杂度

快速排序是递归的,执行时需要一个栈来存放相应的数据。最大递归调用次数与递归输的深度一致,所以最好的情况下的空间复杂度为O 最坏情况下为O(n)

算法特点

1)记录非顺次的移动导致排序方法是不稳定的。

2)排序过程中需要定位表的下界和上界,所以适合用于顺序结构,很难用于链式结构。

3)当n较大时,在平均情况下快速排序是所有内部排序方法中速度最快的一种,所以其适合初始记录无序、n较大时的情况。

猜你喜欢

转载自www.cnblogs.com/huxiaoyang/p/12032004.html
今日推荐