七大常见排序算法
- 直接插入排序
- 希尔排序
- 选择排序
- 堆排序
- 冒泡排序
- 快速排序
- 归并排序
接上文(前四种排序算法详解),后三种排序算法如下:
冒泡排序
算法描述:
- 定义两个下标, i 用来描述数组大小以及判定每次循环 j 的循环次数,j 用来判断比较次数.两个下标搭配使用.
- 循环过程中,j 和 j+1 下标元素进行比较. 两两进行交换.
- 定义 flg 来优化排序.
过程逐步图解:
代码实现:
public class BubbleSort {
public void bubbleSort(int[] array){
for (int i = 0; i < array.length; i++) {
boolean flg = false; // 进行优化
for (int j = 0; j < array.length-1-i; j++) { // 比较次数. 一个长度为 n 的数组需要比较 n-1 次(最坏情况下).
if (array[j] > array[j+1]){
swap(array,j,j+1);
flg = true;
}
}
if (flg == false){ // 当 j 循环完, flg 还为 false,则证明此时已经有序了,不需要 i 向后面走了
return;
}
}
}
private void swap(int[] array,int i,int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
BubbleSort sort = new BubbleSort();
int[] array = {12,8,32,45,56,7};
sort.bubbleSort(array);
System.out.println(Arrays.toString(array));
}
}
输出结果:
时间复杂度:O(n^2)
稳定性:稳定
快速排序
算法描述:
- 以首个元素为基准(pivot),将数组分成两个部分.
- 对这两个部分分别进行排序(递归地进行).
- 找到比基准大的数和比基准小的数,这两数进行交换.
- 当 left 和 right 相遇的时候,将首个元素的值与 left或者right 进行交换.
- 每部分进行递归都会再分成两部分进行排序.
- 重复上述循环,直到数组有序.
过程逐步图解:
代码实现:
public void quickSort2(int[] array){
quick2(array,0,array.length-1);
}
private void quick2(int[] array,int start,int end){
// 为什么取大于号 例子:1,2,3,4 这样一来 1 本身就是最小的,此时 end 进行 -- 的话,下标就会为 -1,越界
if (start >= end){ // 递归结束条件. 当 start 和 end 相遇时,进行终止
return;
}
int pivot = partition2(array,start,end); // 返回一次递归 排序的基准
quick2(array,start,pivot-1); // 对基准左边进行递归 排序
quick2(array,pivot+1,end); // 对基准右边进行递归 排序
}
private int partition2(int[] array,int left,int right){
int temp = array[left]; // 取首个下标元素为基准
int i = left; // 记录首个元素的下标,方便后面 left与right 相遇时进行交换
while (left < right){
while (left < right && array[right] >= temp){ // right 一直向左边 -- ,找到比 temp 小的值
right--;
}
while (left < right && array[left] <= temp){ left 一直向右边 ++ ,找到比 temp 大的值
left++;
}
// 将 left 与 right 下标的值进行交换
swap(array,left,right);
}
// 即 left 和 right相遇
swap(array,left,i);
return left;
}
private void swap(int[] array,int i,int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) { // Hoare法测试
QuickSort sort = new QuickSort();
int[] array = {1,4,3,6,8,2,9,0,5,7};
sort.quickSort2(array);
System.out.println(Arrays.toString(array));
}
运行结果:
时间复杂度:最好情况:O(N*logN) 最坏情况:O(N^2)
稳定性:不稳定
归并排序
算法描述:直接看图解和代码注释更清晰.
逐步图解:
代码实现:
public void mergeSort(int[] array){
int grp = 1; // 先
while (grp < array.length){ // grp 若等于数组大小,即证明数组此时已经有序了,就不需要再次进行排序了
for (int i = 0; i < array.length; i = i + grp*2) {
int left = i;
int mid = left + grp-1;
if (mid >= array.length){ // mid 有可能会越界,进行修正
mid = array.length - 1;
}
int right = mid + grp;
if (right >= array.length){ // right 有可能会越界,进行修正
right = array.length-1;
}
mergeSum(array,left,right,mid); // 进行合并
}
grp = grp * 2; // 每次扩大2倍分组
}
}
private void mergeSum(int[] array,int start,int end,int mid){ // 将排好序的数组进行合并
int s1= start;
int s2 = mid+1;
int[] temp = new int[end-start+1]; // 定义一个新的数组,将比较过的元素放到该数组,即最终这个数组就是两个数组合并的有序数组了
int k = 0; // temp数组下标
while (s1 <= mid && s2 <= end){ // 对分开的两个数组进行比较
if (array[s1] <= array[s2]){ // 第一个数组的第一个元素的值小于第二个数组的第一个元素的值
temp[k] = array[s1]; // 将小的元素放到新的数组中. 那个数组元素小,那个数组的下标就向后面移动
s1++;
k++;
}else {
temp[k] = array[s2];
s2++;
k++;
}
}
while (s1 <= mid){ // 左边数组还有元素
temp[k] = array[s1];
s1++;
k++;
}
while (s2 <= end){ // 右边数组还有元素
temp[k] = array[s2];
s2++;
k++;
}
for (int i = 0; i < temp.length; i++) { // 进行数组合并
array[i+start] = temp[i];
}
}
public static void main(String[] args) {
MergeSort sort = new MergeSort();
int[] array = {1,43,21,3,0,9,11,8};
sort.mergeSort(array);
System.out.println(Arrays.toString(array));
}
运行结果:
时间复杂度:O(n*logn)
稳定性:稳定
总结
算法的分类:
补充:
分治法,字面意思是“分而治之”,就是把一个复杂的问题分成两个或多个相同或相似的子问题,再把子问题分成更小的子问题直到最后子问题可以简单地直接解决,原问题的解就是子问题解的合并。
分治法的基本思想:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
稳定性
稳定:
- 归并排序
- 直接插入排序
- 冒泡排序
不稳定:
- 堆排序
- 快速排序
- 希尔排序
- 选择排序
时间复杂度
- 直接插入排序:O(n^2)
- 希尔排序:O(n^1.3)
- 选择排序:O(n^2)
- 堆排序:O(n*logn)
- 冒泡排序:O(n^2)
- 快速排序:O(n*logn)
- 归并排序:O(n*logn)