排序:将元素按照升序或降序排列(任何Comparable的对象,即任何实现了接口Comparable进而定义了方法compareTo的类的对象,都可以进行排序)
排序是线性表的一种操作。
数据序列:待排序的数据元素的集合。
排序是以关键字为基准进行的。
排序分为内排序和外排序:
内排序:待排序的数据元素全部存储在内存
外排序:待排序的数据元素非常多,不能全部存储在内存中,需要将存储在外部存储介质上的数据分批导入内存,分批排序。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序是稳定的。
排序算法的性能评价:
衡量排序算法性能的重要指标是排序算法的时间复杂度和空间复杂度。
排序算法的时间复杂度由算法 执行中的元素比较次数和移动次数决定。
本此总结采用数组存储数据序列,待排序的数据元素只包含关键字。
- 直接插入排序:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,知道所有的记录插入完为止,得到一个新的有序序列。
算法描述:
(1) 第i(1<=i<=n)趟,数据序列为{a0,a1,…,a(i-1),a(i),…,a(n-1)},其前i个元素构成的子序列{a0,a1,…,a(i-1)}是排好序的,将元素a(i)插入到子序列{ a0,a1,…,a(i-1)}的适当位置,使插入后的子序列仍然是有序的,a(i)的插入位置由关键字比较决定。
(2) 重复上述步骤,n个元素共需要n-1趟扫描,每趟将一个元素插入到它前面子序列中。
图解分析如下:
空间复杂度:O(1);
时间复杂度:O(n^2)
若数据序列已排序,则时间复杂度为O(n)
数据序列越有序,排序越快。
代码实现:
public static void insertSort(int[] array) {
int temp = 0;
int j = 0;
for (int i = 0; i < array.length; i++) {
temp = array[i];
for (j = i - 1; j > 0; j--) {
if (array[j] > temp) {
array[j + 1] = array[j];
} else {
break;
}
}
array[j + 1] = temp;
}
}
- 希尔排序:(称缩小增量法)
(1).将一个数据序列分成若干组,每组由若干相隔一段距离的元素组成,这段距离称为增量,在一个组内采用直接插入排序算法进行排序
(2)增量的初值通常为数据序列长度的一半,以后每趟增量逐渐缩小,最后值为1.随着增量逐渐减小,组数也减小,组内元素个数增加,整个数列则接近有序。当增量为1时,只有一组,元素是整个序列,再进行一趟直接插入排序即可。
图解:
希尔排序的特性:
希尔排序是对直接插入排序的优化;当gap(分的组数或间隔)>1时都是预排序,目的是让数组更接近于有序。当gap==1时,数组已经时接近有序的了,这样就会很快。
希尔排序算法的空间复杂度为O(1)
时间复杂度为O(n1.3-n2)
希尔排序不稳定。
代码如下:
public static void shell(int[] array,int gap ){
int temp = 0;
int j = 0;
for (int i = gap; i < array.length; i++) {
temp = array[i];
for (j = i - gap; j > 0; j-=gap) {
if (array[j] > temp) {
array[j + gap] = array[j];
} else {
break;
}
}
array[j + gap] = temp;
}
}
public static void shellSort(int[] array) {
long start = System.currentTimeMillis();
int[] d={5,3,1}; //分的组数
for(int i=0;i<d.length;i++) {
shell(array, d[i]);
}
long end = System.currentTimeMillis();
System.out.println(end-start);
}
- 冒泡排序:
比较相邻两个元素的关键字值,如果反序,则交换。若按升序排序,每一趟将被扫描的数据序列中的最大元素交换到最后位置,就像气泡从水里冒出来一样。
图解法:
冒泡排序的空间复杂度为O(1),只需要一个辅助空间来用于交换两个元素。
时间复杂度O(n^2),若数据的初始序列已有序,只需要一趟扫描,时间复杂度为O(n)。
冒泡排序算法是稳定的。
代码如下:
public static void booleanSort(int[] array){
boolean exchange=true; //是否交换标记
for(int i=1;i<array.length&&exchange;i++) { //有交换时再进行下一趟
exchange=false; //假定元素未交换
for(int j=0;j<array.length-i;j++) //一趟比较,交换
if(array[j]>array[j+1]){ //反序时,交换
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
exchange=true;
}
}
}
- 快速排序:
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分成两个子序列。每趟从数据序列的两端开始交替进行,将小于基准值的元素交换到序列前端,将大于基准值的元素交换到序列后端,介于两者之间的位置则成为基准值的最终位置。再用同样的方法分别对两个子序列进行排序。
首先选取第一个值49作为基准值,空出第一个元素位置;设i,j分别是数据序列前后两端元素下标,将j位置元素与基准值比较,若小,则移动到序列前端的下标为i的空位置,i加1,此时j位置空出;再将i位置元素与基准值比较,若大,则移动到序列后端的j空位置,j减1;如此重复,直到i与j相等。
(1) 递归方法:
对左右两边子序列用递归方法进行快速排序:
代码:
分成了两个子序列
public static int partion(int[] array,int low,int high) {
int tmp = array[low];
while (low < high) {
while ((low < high) && array[high] >= tmp) {
high--;
}
if (low >= high) {
break;
} else {
array[low] = array[high]; //相遇
}
while ((low < high) && array[low] <= tmp) {
low++;
}
if (low >= high) {
break;
} else {
array[high] = array[low];
}
}
array[low]=tmp;
return low;
}
public static void Insert(int[] array,int start,int end){
int temp=0;
for(int i=start+1;i<=end;i++){
temp=array[i];
int j=0;
for(j=i-1;j>=start;j--) {
if (array[j] > temp) {
array[j + 1] = array[j];
} else {
break;
}
}
array[j+1]=temp;
}
}
将其优化一下,规定选取前多少个调用直接插入排序。
对左右两个子序列进行递归
public static void quick(int[] array,int start,int end){
if(end-start+1<=24){
Insert(array,start,end); //前24个用直接插入排序
return;
}
int par=partion(array,start,end);
if(par>start+1){ //保证左边是否有两个数据及以上
quick(array,start,par-1); //左边递归
}
if(par<end-1){
quick(array,par+1,end); //右边递归
}
}
进行快速排序
public static void quickSort(int[] arr){
long start = System.currentTimeMillis();
quick(arr,0,arr.length-1);
long end = System.currentTimeMillis();
System.out.println(end-start);
}
(2) 三数取中法:
取左端,中间,右端三个数,然后进行排序,将中间数作为枢纽值,通过枢纽值找到基准
保证array[low]<=array[mid]<=arry[hiagh]
图解法:
根据枢纽值进行分割
双向扫描,从左边找大于枢纽值的数,从右边找小于枢纽值的数,然后交换。
代码:
public static void medianofThree(int[] array,int low,int high){
int mid=(low+high)>>>1;
if(array[low]>array[mid]){
swap(array,low,mid);
}
if(array[mid]>array[high]){
swap(array,mid,high);
}
if(array[low]>array[high]){
swap(array,low,high);
}
swap(array,high-1,mid); //枢纽值被放在序列末尾
}
public static void swap(int[] array,int low,int high){
int temp=array[low];
array[low]=array[high];
array[high]=temp;
}
public static void quickSort(int[] array,int low,int high) {
if (low < high) {
//获取枢纽值,并将其放在当前待处理序列末尾
medianofThree(array, low, high);
int mdt = high - 1; //放在当前待处理序列末尾
int i = low;
int j = high - 1; //左右指针
while (true) {
while (array[++i] < array[mdt]) {
}
while (j > low && array[--j] > array[mdt]) {
}
if (i < j) {
swap(array, i, j);
} else {
break;
}
}
if(i<high) {
swap(array, i, high - 1);
}
//分段递归
quickSort(array, low, i - 1);
quickSort(array, i + 1, high);
}
}
(3) 快速排序非递归实现:
只要使用一个栈来保存区间就可以了。
一般将递归程序改成非递归首先想到的就是使用栈,因为递归本身就是一个压栈的过程
图解:
代码实现:
public static void quickSort1(int[] array) {
int[] stack = new int[array.length * 2];
int top = 0;
int low = 0;
int high = array.length - 1;
//先进性一趟快排
int par = partion(array, low, high);
//1.判断当前par的左右两边是否有两个数据以上
if (par > low + 1) {
stack[top++] = low;
stack[top++] = par - 1;
}
if (par < high - 1) {
stack[top++] = par + 1;
stack[top++] = high;
}
//两边的数对已经全部入栈
//需要做的就是判断栈是否为空,不为空,取出两个数对
while (top > 0) { //表明栈不为空
high = stack[--top];
low = stack[--top];
par = partion(array, low, high);
//1.判断当前par的左右两边是否有两个数据以上
if (par > low + 1) {
stack[top++] = low;
stack[top++] = par - 1;
}
if (par < high - 1) {
stack[top++] = par + 1;
stack[top++] = high;
}
}
}
快速排序最好情况下空间复杂度为:O(log(2)n),最坏情况下O(n),平均空间复杂度为O(log(2)n)
时间复杂度:最好的情况O(nlogn),最坏的情况O(n^2),平均时间复杂度为O(nlogn).
快速排序算法是不稳定的。