冒泡排序、选择排序、插入排序、归并排序
冒泡排序
通过交换的方式:若当前元素比下一个元素大,则两个元素交换. 每一趟扫描区域中最大的元素就位.
JAVA代码
public class Sort_Bubble {
public void bubbleSort(int[] arr){
if (arr == null || arr.length<2) return;
for(int len = arr.length-1; len>0; len--){
for(int i = 0; i< len; i++){
if (arr[i] > arr[i+1]){
swap(arr, i, i+1);
}
}
}
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
时间复杂度O(n^2),空间复杂度O(1).
稳定性:稳定.
选择排序
每次从无序的里面选出最大的元素.(冒泡排序也是一种选择排序)
小步慢跑式移动导致效率低.
java代码`
public class Sort_Selection {
public static void selectionSort(int[] arr){
if(arr == null || arr.length<2) return;
for(int start=0; start<arr.length;start++){
int minIndex = start;
for(int i= start; i<arr.length; i++){
minIndex = arr[i] < arr[minIndex] ? i:minIndex;
}
swap(arr, start, minIndex);
}
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
时间复杂度O(n^2),空间复杂度O(1).
稳定性:不稳定.
插入排序
当前元素按次序插到有序序列中.
Java代码:
public class Sort_Insertion {
public static void insertionSort(int[] arr){
for(int i=1; i<arr.length; i++){
for (int curr=i; curr>0 && arr[curr-1]>arr[curr]; curr--){ //当前元素在有效位置并且比前一个元素小,则交换
swap(arr, curr-1,curr);
}
}
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
时间复杂度:最好O(n),最坏O(n^2). 空间复杂度O(1).
稳定性:稳定.
归并排序
分治策略:
- 序列一分为二 //O(1)
- 子序列递归排序 // 2*T(n/2)
- 合并有序子序列 //O(n)
JAVA代码
public class Sort_Merge {
public static void mergeSort(int[] arr){
if (arr == null || arr.length<2) return;
sortProcess(arr, 0, arr.length-1);
}
//排序
public static void sortProcess(int[] arr, int L, int R){
if (L == R) return;
int mid = L + ((R-L)>>1);
sortProcess(arr, L, mid);
sortProcess(arr, mid+1, R);
merge(arr, L, mid, R);
}
//归并
public static void merge(int[] arr, int L, int mid, int R){
int[] help = new int[R-L+1];
int i = L;
int j = mid+1;
int k = 0;
while (i<=mid && j<=R){
help[k++] = arr[i]<=arr[j] ? arr[i++]: arr[j++];
}
//有且仅有一个子序列没有被访问完
while (i<=mid)
help[k++] = arr[i++];
while (j<=R)
help[k++] = arr[j++];
//合并后的序列重新装回arr
for (i = 0; i<help.length; i++)
arr[L+i] = help[i];
}
}
时间复杂度:T(n) = 2T(n/2) + O(n) =O(nlogn)
空间复杂度: 临时数组+压栈 O(n) + O(logn) = O(n)
稳定性:稳定.
快速排序
1. 经典快排
每次以无序部分最后一个元素x为基准,一分为二:
左:小于等于x,
右:大于x。
JAVA代码:
public class Sort_Quick {
/*************经典快排*************/
public static void quickSort1(int[] arr, int L, int R){
if (L < R) {
int posi = position1(arr, L, R); //切分的位置
quickSort1(arr, L, posi - 1); //左部分排序
quickSort1(arr, posi + 1, R); //右部分排序
}
}
public static int position1(int[] arr, int L, int R){
int posi;
int less = L-1;
int more = R;
int i = L;
while (i < more){
if (arr[i] <= arr[R]) swap1(arr, ++less, i++); //当前元素小于等于最后一个元素,小于等于部分后一个元素与当前元素交换.
else if (arr[i] > arr[R]) swap1(arr, --more, i); //当前元素比最后一个元素大,当前元素与大于部分的前一个元素交换
}
swap1(arr, more, R);
posi = more;
return posi;
}
public static void swap1(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
时间复杂度:最坏情况O(n^2) , 平均情况 O(nlogn)
空间复杂度:最坏情况O(n),平均情况 O(logn)
稳定性:稳定.
2. 改进快排
与经典快排不同的是,改进快排是将小于x的作为左部分。省去了对相同元素的操作.
JAVA代码:
public class Sort_Quick {
public static void quickSort2(int[] arr, int L, int R){
if (L < R) {
int[] posi = position2(arr, L, R); //切分的位置
quickSort1(arr, L, posi[0] - 1); //左部分排序
quickSort1(arr, posi[1] + 1, R); //右部分排序
}
}
public static int[] position2(int[] arr, int L, int R){ //大小为2的数组保存切分点的位置
int[] posi = new int[2];
int less = L-1;
int more = R;
int i = L;
while (i < more){
if (arr[i] < arr[R]) swap2(arr, ++less, i++); //当前元素比最后一个元素小,继续向后遍历.
else if (arr[i] > arr[R]) swap2(arr, --more, i); //当前元素比最后一个元素大,当前元素与大于部分的前一个元素交换
else i++; //当前元素等于最后一个元素,不作任何操作继续向后扫描
}
swap2(arr, more, R);
posi[0] = less+1;
posi[1] = more;
return posi;
}
public static void swap2(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
时间复杂度:最坏情况O(n^2) , 平均情况 O(nlogn),但常数项比经典快排小.
空间复杂度:最坏情况O(n),平均情况 O(logn).
稳定性:稳定.
3. 随机快排
当数组完全逆序时,每次只能将数组分出左部分,每次只固定一个元素,因此花费的时间为O(n^2).
与经典快排不同的是,随机快排每次以无序部分随机位置上的元素为基准。花费时间的期望值为O(nlogn).
JAVA代码:
public class Sort_Quick {
/*************随机快排*************/
public static void quickSort3(int[] arr, int L, int R){
if (L < R) {
swap3(arr, L + (int)(Math.random()*(R-L+1)), R);//随机选择一个位置的元素作为基准,并放在最后
int[] posi = position3(arr, L, R); //切分的位置
quickSort1(arr, L, posi[0] - 1); //左部分排序
quickSort1(arr, posi[1] + 1, R); //右部分排序
}
}
public static int[] position3(int[] arr, int L, int R){ //大小为2的数组保存切分点的位置
int[] posi = new int[2];
int less = L-1;
int more = R;
int i = L;
while (i < more){
if (arr[i] < arr[R]) swap3(arr, ++less, i++); //当前元素比最后一个元素小,继续向后遍历.
else if (arr[i] > arr[R]) swap3(arr, --more, i); //当前元素比最后一个元素大,当前元素与大于部分的前一个元素交换
else i++; //当前元素等于最后一个元素,不作任何操作继续向后扫描
}
swap3(arr, more, R);
posi[0] = less+1;
posi[1] = more;
return posi;
}
public static void swap3(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
期望时间复杂度:T(n) = 2T(n/2)+O(n) = O(nlogn).
空间复杂度:O(logn).
稳定性:稳定.
堆排序
大根堆中最大的数为根的值,则只要每次将无序部分形成大根堆,将大根堆中的根加入有序系列。重复此过程直到整个序列有序。
- 无序部分形成大根堆。
- 大根堆中的根与最后一个元素互换。
- 最后一个元素加入有序部分。
- 堆下沉继续形成大根堆。
JAVA代码:
public class Sort_Peach {
public void peachSort(int[] arr){
if (arr == null || arr.length<2) return;
//构建大根堆
for (int i = 0; i<arr.length; i++){
heapInsert(arr, i); //0——i 位置的大根堆
}
int heapSize = arr.length;
swap(arr, 0, --heapSize); //根结点与最后一个元素交换
//当大根堆的数量为0,则整个数组有序。
while(heapSize>0){
heapify(arr, 0, heapSize); //根节点下沉
swap(arr,0, --heapSize); //根节点与无序部分最后一个元素交换。
}
}
/*****形成大根堆***/
public static void heapInsert(int[] arr, int index){
while (arr[index]> arr[(index-1)/2]){ //当前结点小于父节点或当前结点为根结点都会停止。
swap(arr, index, (index-1)/2);
index = (index-1)/2;
}
}
/*******下沉******/
public static void heapify(int[] arr, int index, int heapSize){
int left = index*2+1;
while(left < heapSize){
int largest = left+1<heapSize && arr[left]<arr[left+1] //
? left+1 //左右孩子存在时,右孩子更大时
: left; //右孩子不存在或左孩子大时
largest = arr[largest] < arr[index] ? index: largest; //比较当前结点和左右孩子
if (largest == index) break; //当前结点比左右孩子大,那么不用变换
//左或右孩子比当前结点大,则交换
swap(arr, index, largest);
index = largest;
left = index*2+1;
}
}
/******交换函数******/
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
时间复杂度:初始化堆:log1+log2+…+logn = O(n)
heapify过程,一共调用n次heapify, heapify()时间复杂度为O(logn),即O(nlogn)
所以时间复杂度为:O(n) + O(nlogn) = O(nlogn).
桶排序
不基于比较而是基于桶. 与被排序的数据状况有关,并不常用
时间复杂度O(n) ,空间复杂度O(n) .稳定的排序。