七大排序算法
前言
本篇博客将根据现有知识对数据结构七大排序算法做以小结,以下博客仅作为个人学习过程的小结,如能对各位博友有所帮助不胜荣幸。
本篇博客将简单介绍七大排序算法的概念原理应用,以及各自优点及区别,只做本人小结,后期随学习深入再做补充修改。
七大排序算法总览
插入排序
原理
类似于插入扑克牌,将整个区间分为有序区间和无序区间两个,每次从无序区间中取出第一个元素,与有序区间中的元素逐一比较,插入到合适位置,重复以上操作直到无序区间元素被区完
实现
//初始时:
//无序区间:[0,1)
//有序区间:[1,arr.length-1]
public static void insertSort(int[] arr){
for(int i = 1; i < arr.length; i++){
int val = arr[i];
//无序区间中的第一个元素
int j;
for(j = i-1; j >= 0 && arr[j] > val; j--){
//有序区间从后向前遍历
arr[j+1] = arr[j];
//有序区间中遍历到的该元素a[j]>判断值val,则该元素在数组中的位置向后移一位
}
arr[j+1] = val;
}
}
性能
时间复杂度:
最好:O(n) 最坏:O(n²) 平均:O(n²)
空间复杂度:
O(1)
优化:二分插入排序
因为插入排序总是在无序区间中找元素,然后遍历有序区间查找合适的位置插入,而因为区间有序,所以在有序区间查找位置时可以用二分查找的办法提高效率
希尔排序
原理
希尔排序是插入排序的优化,其基本思想为先选定一个整数 len,将整个区间分组,分组依据为,所有距离为 len 的元素在同一组,并对每组内部进行插入排序,然后减小len的值,重复以上操作,直到len == 1
实现
public static void shellSort(int[] arr){
int len = arr.length;
while(len > 1){
gapInsertSort(arr,len);
len = (len / 3) + 1;
}
gapInsertSort(arr,1);
}
private static void gapInsertSort(int[] arr,int len){
for(int i = 1; i < arr.length; i++){
int val = arr[i];
int j = i-len;
for(; j >= 0 && arr[j] > val; j-=len){
arr[j+len] = arr[j];
}
arr[j+len] = val;
}
}
性能
时间复杂度:
最好:O(n) 最坏:O(n²) 平均:O(n^1.3)
空间复杂度:
O(1)
选择排序
原理
每次在无序端寻找最大(小)的元素,将其放到无序序区间的最后端(前端),即遍历找最值放到有序区间的两端,直到无序区间被遍历完
实现
public static void selectSort(int[] arr){
//无序区间[0,array.length)
//有序区间(array.length,array.length)
for(int i = arr.length; i > 0; i--){
int j = 0;
int index = 0;
for(; j < i ; j++){
if(arr[j] > arr[index]){
index = j;
}
}
int t = arr[index];
arr[index] = arr[i-1];
arr[i-1] = t;
}
}
性能
时间复杂度:最坏/最好/平均 O(n²)
空间复杂度:O(1)
优化:双向选择排序
因为选择排序总是要整体遍历一遍无序数组,在其中找到最值,则可以通过一次遍历同时找到最大值和最小值,分别放到无序区间的两端,这样就对选择排序提高了一半的效率
堆排序
原理
其基本思想和选择排序一样,也是每次在无序区间中寻找最值,放到两端,而不同的是堆排序中查找最值的方式是通过堆来选择最值
注意:升序排序建大堆,降序排序建小堆
实现
public static void heapSort(int[] arr){
buildHeap(arr);
for(int i = arr.length-1; i >= 0; i--){
//交换前
//无序数组[0,i]
//有序数组(i,arr.length)
swap(arr,i,0);
shiftDown(arr,i,0);
//交换后
//无序数组[0,i-1]
//有序数组(i-1,arr.length)
//无序数组长度:i
}
}
//建堆
private static void buildHeap(int[] arr){
int index = (arr.length-1-1)/2;
for(int i = index; i >= 0; i--){
shiftDown(arr,arr.length,i);
}
}
//交换数组两元素
private static void swap(int[] arr,int a,int b){
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
//堆向下调整
private static void shiftDown(int[] arr,int size,int index){
int left = index * 2 + 1;
while(left < size){
int max = left;
int right = left+1;
if(right < size){
if(arr[right] > arr[max]){
max = right;
}
}
if(arr[index] >= arr[max]){
break;
}
swap(arr,index,max);
index = max;
left = index * 2 + 1;
}
}
性能
时间复杂度:
O(n * log(n))
空间复杂度:
O(1)
冒泡排序
原理
无序区间中,逐次遍历两相邻元素比较,小的在前大的在后,每次将最大数冒泡到无序区间最后,重复以上操作,直到整体有序
实现
public static void bubbleSort(int[] arr){
for(int i = arr.length-1; i >= 0; i--){
boolean isSort = true;
//无序数组:[0,i)
//有序数组:[i,arr.length)
for(int j = 0; j < i; j++){
if(arr[j] > arr[j+1]){
swap(arr,j,j+1);
isSort = false;
}
}
if(isSort){
break;
//优化,如果无序区间遍历一次未有元素进行交换,则表明有序
}
}
}
性能
时间复杂度:
最好:O(n) 最坏:O(n²) 平均:O(n²)
空间复杂度:
O(1)
快速排序
原理
- 在区间中选择一个数(val),作为基准下标为(index)
- 遍历整个区间,小于 val 的放 index 的左边,大于 val 的放 index 的右边,将整个区间分为三段 [0 , index) 、[index] 、(index , arr.length-1]
- 再对左右两区间进行如上操作,直到左右区间长度为1表示已有序,或长度为0表示无数据
实现
private static void quickSortFunction(int[] arr,int start,int end){
if(start == end){
return;
}
if(start > end){
return;
}
//为方便起见,每次将index起始位置定为start
int index = partition(arr,start,end);
quickSortFunction(arr,start,index-1);
//index 之前的都是比 基准值 小的
quickSortFunction(arr,index+1,end);
//index 之前的都是比 基准值 大的
}
private static int partition(int[] arr, int start, int end) {
int left = start;
int right = end;
while (left < right){
while (left < right && arr[left] <= arr[start]){
left++;
}
while (left < right && arr[right] >= arr[start]){
right--;
}
swap(arr,left,right);
}
swap(arr,start,right);
return right;
}
性能
时间复杂度:
最好:O(nlog(n)) 最坏:O(n²) 平均:O(nlog(n))
空间复杂度:
最好:O(log(n)) 最坏:O(n) 平均:O(log(n))
优化
- 基准值的选择,可以取两边界值、随机取值、多数中取中间值
- partition的选择
- 当区间的元素数量小到一个阈值时,可以考虑用插入排序
归并排序
原理
归并排序采用采用的是归并操作思想,将一个区间分为两个区间,在将每个子区间细分为两个区间,直到每个子区间在各自区间上都有序,再将子区间合并使合成后的区间有序,直到最终合成为一个区间,即将区间分割成若干的有序区间,在将有序区间两两合并
实现
public static void mergeSort(int[] arr){
mergeSortFunction(arr,0,arr.length);
//待排序区间[0,arr.length)
}
private static void mergeSortFunction(int[] arr, int low, int high) {
if(low + 1 >= high){
return;
}
int mid = (low + high) / 2;
mergeSortFunction(arr,low,mid);
mergeSortFunction(arr,mid,high);
merge(arr,low,mid,high);
}
private static void merge(int[] arr, int low, int mid, int high) {
int i = low;
int j = mid;
int len = high-low;
int[] newArray = new int[len];
int k = 0;
while(i < mid && j < high){
if(arr[i] < arr[j]){
newArray[k++] = arr[i++];
}else{
newArray[k++] = arr[j++];
}
}
while (i < mid){
newArray[k++] = arr[i++];
}
while (j < high){
newArray[k++] = arr[j++];
}
for(k = 0; k < len; k++){
arr[low+k] = newArray[k];
}
}
性能
时间复杂度:O(n*log(n))
空间复杂度:O(n)
对海量数据进行排序
外部排序:排序过程需用到磁盘等外部储存空间竞选排序
前提:内存只有1G,需要排序100G
因为内存中因为无法一次放下所有数据,所以需要外部排序,其中归并排序是最常用的外部排序
- 将数据分为200份,每份512m
- 分别对每份数据放到内存中进行排序,在写回磁盘中
- 进行200路归并,对200分自身有序文件同时左归并排序,排序好的数据写会磁盘
排序算法的稳定性
概念:
何为排序稳定?即在对一个无序区间排序时,参照值相同的数据,排序前后相对位置没有改变,则称该排序算法稳定
具有稳定性的排序算法:
冒泡、插排、归并
稳定性存在的意义:
那么既然比较的两个数据值一样,那考虑谁先谁后有什么意义呢?
当对一堆具有复杂属性的数据进行排序时,由可能排序时只按其中的一个属性进行排序,此时如果排序不稳定,则会导致两个值相同的数据位置混乱,造成混淆。
七大排序算法的比较
以上便是对七大排序算法的知识点小结,随着后续学习的深入还会同步的对内容进行补充和修改,如能帮助到各位博友将不胜荣幸,敬请斧正