七种排序算法的实现:
1、直接插入排序
2、希尔排序
3、选择排序
4、堆排序
5、冒泡排序
6、快速排序
7、归并排序
七种排序算法性能分析:
排序方法 | 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(N) | O(N^2) | O(N^2) | O(1) | 稳定 |
插入排序 | O(N) | O(N^2) | O(N^2) | O(1) | 稳定 |
选择排序 | O(N^2) | O(N^2) | O(N^2) | O(1) | 不稳定 |
希尔排序 | O(N) | O(N^1.3) | O(N^2) | O(1) | 不稳定 |
堆排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(1) | 不稳定 |
快速排序 | O(NlogN) | O(NlogN) | O(N^2) | O(logN)~O(N) | 不稳定 |
归并排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(N) | 稳定 |
一、直接插入排序
- 将整个区间划分为已排序区间和待排序区间,每次选择待排序区间的第一个元素,在已排序区间内选择合适位置插入。
- 代码实现:
public static void insertSort(int[] arr){
//将整个数组区间分为已排序区间和待排序区间。初始已排序区间可为第一个元素
//已排序区间 [0,1)
//待排序区间 [1,arr.length)
int bound = 1;
//外层循环表示待排序插入的次数
for(;bound < arr.length;bound++){
int v = arr[bound];//待排序的第一个元素
int cur = bound - 1;//已排序的最后一个元素
//里层循环寻找待排序元素要插入的位置
for(;cur >= 0;cur--){
if(arr[cur] > v){//若写成 <=,有相等元素时就变为不稳定排序了
//比待排序元素大,需往后搬移一个位置
arr[cur + 1] = arr[cur];
}else{
//当前cur位置的元素 < 待排序元素小,即已找到插入位置,循环结束
break;
}
}
//将元素插入到cur后面一个位置
arr[cur + 1] = v;
}
}
- 算法性能分析:
时间复杂度 | 空间复杂度 | 稳定性 | ||
---|---|---|---|---|
最好 | 平均 | 最坏 | ||
O(N) | O(N^2) | O(N^2) | O(1) | 稳定 |
数据有序 | 数据逆序 |
-
直接插入排序的两个特点:
若数组长度比较短,排序效率较高;
若数组相对有序,排序效率也较高。
二、希尔排序(直接插入排序的改进)
- 基本思想:针对整个序列进行分组(使用间隔系数gap作为分组的依据),然后对每一组再进行直接插入排序。随之调整gap的值,重复上述分组和排序,直至gap=1,即对序列进行直接插入排序,使整个序列有序。
- 代码实现:
//希尔排序
public static void shellSort(int[] arr){
int gap = arr.length / 2;
while(gap >= 1){
_shellSort(arr,gap);
gap = gap / 2;
}
}
//进行分组直接插入排序
public static void _shellSort(int[] arr,int gap){
int bound = gap;
for(;bound < arr.length;bound++){
int v = arr[bound];
int cur = bound - gap;
for(;cur >= 0;cur -= gap){
if(arr[cur] > v){
arr[cur + gap] = arr[cur];
}else{
break;
}
}
arr[cur + gap] = v;
}
}
- 算法性能分析:
时间复杂度 | 空间复杂度 | 稳定性 | ||
---|---|---|---|---|
最好 | 平均 | 最坏 | ||
O(N) | O(N^1.3) | O(N^2) | O(1) | 不稳定 |
数据有序 |
不稳定原因:若有相同元素在不同组中,排序会导致不稳定排序;若在同一组中,则稳定(同一组中采用的是稳定的插入排序)。
三、选择排序
- 基本思想:从前向后遍历数组,从未排序数组中选择最小的元素和未排序数组中的第一个元素进行交换。或者从后往前遍历数组,从未排序数组中选择最大的元素和未排序数组中的最后一个元素进行交换。
- 将整个数组区间分为两个区间,已排序区间和待排序区间。遍历待排序区间,以待排序区间的开始位置作为擂台,通过打擂台的方式找出这个区间中的最小元素。
- 代码实现:
//选择排序
public static void seletSort(int[] arr){
//已排序区间[0,bound)
//待排序区间[bound,arr.length)
int bound = 0;
for(;bound < arr.length;bound++){
for(int cur = bound + 1;cur < arr.length;cur++){
if(arr[cur] < arr[bound]){
int tmp = arr[cur];
arr[cur] = arr[bound];
arr[bound] = tmp;
}
}
}
}
- 算法性能分析:
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
O(N^2) | O(1) | 不稳定 |
四、堆排序
- 基本思想:排升序建大堆;排降序建小堆。(目的:满足原地排序的要求)
- 代码实现:
以升序为例,创建大堆,循环删除堆顶元素(将堆顶元素与最后一个元素交换),每删除一个堆顶元素,对堆进行向下调整,使其满足大堆要求。
此过程相当于每次将堆中最大元素交换到数组末尾,随循环进行,使得数组从后往前依次有序。
//堆排序
public static void heapSort(int[] arr){
//1、创建堆
createHeap(arr);
int heapSize = arr.length;
//2、删除堆顶元素(交换堆顶和最后一个元素,再size--)
//交换后,数组最后一个元素就为最大值,size--,堆长度-1,将最大值从堆顶删除,
//此时再对堆进行向下调整,再取下一个最大值,依次循环使得数组从后往前依次有序......
while(heapSize > 0) {
swap(arr, 0, heapSize - 1);
heapSize--;//heapSize--,对数组长度不产生影响
shiftDown(arr,heapSize,0);
}
}
//向下调整
public static void shiftDown(int[] arr,int size,int index){
int parent = index;
int child = 2 * parent + 1;
while(child < size){
if(child + 1 < size && arr[child + 1] > arr[child]){
child = child + 1;
}
if(arr[parent] < arr[child]){
swap(arr,parent,child);
}else{
break;
}
parent = child;
child = 2 * parent + 1;
}
}
//创建堆
public static void createHeap(int[] arr){
for(int i = (arr.length - 1 - 1) / 2;i >= 0;i--){
shiftDown(arr,arr.length,i);
}
}
public static void swap(int[] arr,int x,int y){
int temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
- 算法性能分析:
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
O(NlogN) | O(1) | 不稳定 |
五、冒泡排序
- 基本思想:在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序。
- 代码实现:
//冒泡排序
public static void bubbleSort(int[] arr){
//外层循环控制比较趟数,每一趟完都将最大元素放到最后一位
for(int i = 0;i < arr.length;i++){
//里层循环进行相邻两元素的比较和交换操作
for(int j = 1;j < arr.length;j++){
if(arr[j - 1] > arr[j]){
swap(arr,j - 1,j);
}
}
// for(int j = arr.length - 1;j > 0;j--){
// if(arr[j] < arr[j - 1]){
// swap(arr,j,j - 1);
// }
// }
}
}
- 算法性能分析
时间复杂度 | 空间复杂度 | 稳定性 | ||
---|---|---|---|---|
最好 | 平均 | 最差 | ||
O(N) | O(N^2) | O(N^2) | O(1) | 稳定 |
数据有序 | 数据逆序 |
六、快速排序
-
基本思想:
以升序为例,核心partition方法:选取最后一个元素为基准值,创建两个引用 left=0 和 right=length-1,left++从左往右找一个比基准值大的元素,right–从右往左找一个比基准值小的元素,找到后将两者交换,继续循环执行,直到left==right,将此位置的元素与基准值交换位置。以此位置为界,左边元素都比基准值小,右边比基准值大,对左右两边分别进行递归,使整个数组有序。 -
代码实现:
递归实现:
//快速排序(递归实现)
public static void quickSort(int[] arr){
_quickSort(arr,0,arr.length - 1);
}
public static void _quickSort(int[] arr,int left,int right){
if(left >= right){
//只有一个元素或没有元素,不需要排序
return;
}
//[left,right]
int index = partition(arr,left,right);
_quickSort(arr,left,index - 1);
_quickSort(arr,index + 1,right);
}
//partition方法返回值表示整理完当前区间后,基准值所在的位置,
//即遍历过程中left和right重合的位置
public static int partition(int[] arr,int left,int right) {
int q = arr[right];//选取最后一个元素为基准值
int i = left;
int j = right;
while (i < j) {
while (i < j && arr[i] <= q) {
i++;
} //循环结束,i指向一个比基准值大的元素
while (i < j && arr[j] >= q) {
j--;
} //循环结束,j指向一个比基准值小的元素
//找到左侧比基准值大的元素和右侧比基准值小的元素后,将两元素交换
swap(arr, i, j);
} //循环结束,i 和 j 重合
//交换重合位置元素和基准元素,使得基准值左侧元素均比基准值小,右侧元素均比基准值大
swap(arr,i,right);
return i;
}
非递归实现:(借助栈实现)
创建一个栈,将待处理区间入栈,循环取栈顶元素的区间,对其进行partition操作,后将得到的子区间入栈,直至栈为空时,循环结束,排序完成。
//快速排序(非递归) 相似与二叉树的非递归先序遍历
public static void quickSortByLoop(int[] arr){
//1、创建栈,保存当前每一个待处理的区间
Stack<Integer> stack = new Stack<>();
//2、将第一组要处理的区间入栈(将根节点入栈)
stack.push(0);
stack.push(arr.length - 1);
//3、循环取栈顶元素的区间,进行partition操作
while(!stack.isEmpty()){
int right = stack.pop();
int left = stack.pop();
if(left >= right){
continue;
}
//4、调用partition方法,进行整理
int index = partition(arr,left,right);
//5、将得到的子区间入栈[left,index - 1] [index + 1,right]
//(右子树入栈)
stack.push(index + 1);
stack.push(right);
//(左子树入栈)
stack.push(left);
stack.push(index - 1);
}
}
public static int partition(int[] arr,int left,int right) {
int q = arr[right];//选取最后一个元素为基准值
int i = left;
int j = right;
while (i < j) {
while (i < j && arr[i] <= q) {
i++;
} //循环结束,i指向一个比基准值大的元素
while (i < j && arr[j] >= q) {
j--;
} //循环结束,j指向一个比基准值小的元素
//找到左侧比基准值大的元素和右侧比基准值小的元素后,将两元素交换
swap(arr, i, j);
} //循环结束,i 和 j 重合
//交换重合位置元素和基准元素,使得基准值左侧元素均比基准值小,右侧元素均比基准值大
swap(arr,i,right);
return i;
}
- 算法性能分析
时间复杂度 | 空间复杂度 | 稳定性 | ||||
---|---|---|---|---|---|---|
最好 | 平均 | 最坏 | 最好 | 平均 | 最坏 | |
O(N*logN) | O(N*logN) | O(N^2) | O(logN) | O(logN) | O(N) | 不稳定 |
七、归并排序
- 基本思想:将已有序的子序列合并,得到完全有序的序列;即先使每个子
序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 - 代码实现
递归实现:将数组一分为二,分别递归数组左右区间,让数组左右区间均归并为有序数组,再合并两个区间。
//归并排序
public static void mergeSort(int[] arr){
_mergeSort(arr,0,arr.length);
}
//辅助递归方法
public static void _mergeSort(int[] arr,int left,int right){
if(right - left <= 1){//[left,right) 区间只有一个元素或没有元素
return;
}
int mid = (left + right) / 2;
//先让左区间有序[left,mid)
_mergeSort(arr,left,mid);
//让右区间有序[mid,right)
_mergeSort(arr,mid,right);
//合并两个有序区间
merge(arr,left,mid,right);
}
//此方法将两个有序数组进行归并
//[left,mid) [mid,right)
public static void merge(int[] arr,int left,int mid,int right){
if(left >= right){
return;//空区间直接返回
}
//创建临时空间数组用来保存归并结果
int[] temp = new int[right - left];
int l = left;
int r = mid;
int tempIndex = 0;
while (l < mid && r < right) {
if (arr[l] <= arr[r]) { //<= 保证稳定性
temp[tempIndex] = arr[l];
tempIndex++;
l++;
}else{
temp[tempIndex] = arr[r];
tempIndex++;
r++;
}
}
//循环结束后,将剩余元素添加到最终结果中
while(l < mid){
temp[tempIndex] = arr[l];
tempIndex++;
l++;
}
while(r < right){
temp[tempIndex] = arr[r];
tempIndex++;
r++;
}
//将temp中的元素返回到arr数组中(保证原地排序)
//temp对应的是原数组中[left,right)区间的数组
for(int i = 0;i < temp.length;i++){
arr[left + i] = temp[i];
}
}
非递归实现:
首先将长度为1的两个相邻数组归并,再将长度为2的两个相邻数组归并,依次类推,直到整个数组归并完成。(使用gap改变待归并数组的长度)
//归并排序(非递归)
public static void mergeSortByLoop(int[] arr){
int gap = 1;
for(;gap < arr.length;gap *= 2){//gap为待归并数组的长度 第一次是长度为1的两个数组归并,
// 第二次是两个长度为2的数组归并...,即以长度2*gap的两个数组归并
for(int i = 0;i < arr.length;i += 2*gap){
//一个数组的长度时gap,每次循环合并两个gap长的数组,
// 所以下次循环从i+2*gap开始循环
//确定要归并的两个数组的区间[left,mid)和[mid,right)
int left = i;
int mid = i + gap;
//防止数组溢出,大于数组长度时,置mid = arr.length,此时最后一个区间就为空区间了
if(mid >= arr.length){
mid = arr.length; //eg:[7,7)为空区间,不必处理
}
int right = i + 2 * gap;
if(right >= arr.length){//同上
right = arr.length;
}
merge(arr,left,mid,right);
}
}
}
//此方法将两个有序数组进行归并
//[left,mid) [mid,right)
public static void merge(int[] arr,int left,int mid,int right){
if(left >= right){
return;//空区间直接返回
}
//创建临时空间数组用来保存归并结果
int[] temp = new int[right - left];
int l = left;
int r = mid;
int tempIndex = 0;
while (l < mid && r < right) {
if (arr[l] <= arr[r]) { //<= 保证稳定性
temp[tempIndex] = arr[l];
tempIndex++;
l++;
}else{
temp[tempIndex] = arr[r];
tempIndex++;
r++;
}
}
//循环结束后,将剩余元素添加到最终结果中
while(l < mid){
temp[tempIndex] = arr[l];
tempIndex++;
l++;
}
while(r < right){
temp[tempIndex] = arr[r];
tempIndex++;
r++;
}
//将temp中的元素返回到arr数组中(保证原地排序)
//temp对应的是原数组中[left,right)区间的数组
for(int i = 0;i < temp.length;i++){
arr[left + i] = temp[i];
}
}
- 算法性能分析
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
O(NlogN) | O(N) | 稳定 |