常见排序算法
排序:所谓排序,就是使一组元素,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的
内部排序:数据元素全部放在内存中的排序
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序
排序算法的时间复杂度、空间复杂度及稳定性
1 直接插入排序
1.1 基本思想
直接插入排序是一种简单的插入排序算法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。元素集合越接近有序,直接插入排序算法的时间效率越高。
1.2 算法步骤
当插入第i(i>=1)个元素时,前面的元素arr[0],arr[1],... arr[i-1]已经排好序,此时比较arr[i]和arr[i-1],arr[i-2],... arr[0], 找到插入位置将arr[i]插入,原来位置上的元素顺序后移(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入相等元素的后面)。
1.3 图解
1.4 代码实现
1.4.1 直接插入排序
//常用
public static void InsertSort_1(int[] arr){
/*一次处理一个数,循环arr.length次
有序[0,i-1]
排序 [i]
无序[i+1,arr.length-1]*/
for(int i=0; i<arr.length; i++) {
int key = arr[i];
int j = 0;
//遍历有序区间 [0,i-1]
for (j = i-1; j >= 0 && key<arr[j]; j--) {
arr[j + 1] = arr[j];
}
arr[j + 1] = key;
}
}
public static void InsertSort_2(int[] arr){
for(int i=0;i<arr.length;i++){
int key = arr[i];
int j;
for(j=i-1;j>=0 && arr[i]<arr[j];j--){
}
for(int k=i;k>j+1;k--){
arr[k]=arr[k-1];
}
arr[j+1]=key;
}
}
1.4.2 利用二分查找实现插入排序
//利用二分查找实现插入排序
public static void InsertSortBS(int[] arr){
for(int i=0;i<arr.length;i++) {
int left=0;
int right=i-1;
int key = arr[i];
//[left,right] 左闭右闭
while(left<=right){
int mid = left+(right-left)/2;
if(arr[mid]==key){
left = mid+1;
}else if(arr[mid]<key){
left = mid+1;
}else{
right = mid-1;
}
}
//left是要插入的位置下标
for(int j=i;j>left;j--){
arr[j]=arr[j-1];
}
arr[left] = key;
}
}
2 希尔排序
2.1 基本思想
先将整个待排序的记录分割成若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行直接插入排序。
2.2 算法步骤
1)将待排序序列依据组间隔(gap)划分为若干组,对每组分别进行插入排序。
2)初始时,gap=size/3+1(或 gap=size/2),此时增量最大,因此每个分组内数据项个数相对较少,利用插入排序即可。
3)至此,完成一次排序,更新组间隔gap=gap/3+1(或 gap=gap/2),每个分组内数据项个数相对增加,但已进行依次排序,所以数据基本有序,再进行插入排序。
4)重复步骤 3)直到组间隔为1时,所有数据项在同一组内进行插入排序。至此,排序完成。
组间隔(gap):gap初始值为size 之后不断更新为gap=gap/3+1 或 gap=gap/2
当gap>1时都是预排序,目的是让数组更接近有序。当gap==1时,数组已接近有序,这样再进行排序会很快
2.3 图解
2.4 代码实现
public static void ShellSort(int[] arr){
int gap = arr.length;
while(true){
gap=gap/3+1;
for(int i=0; i<arr.length; i++){
int key=arr[i];
int j;
for(j=i-gap;j>=0 && key<arr[j];j-=gap){
arr[j+gap] = arr[j];
}
arr[j+gap] = key;
}
if(gap==1) {
break;
}
}
}
3 选择排序
3.1 基本思想
每一次直接从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
3.2 算法步骤
1)在未排序序列中找到最大(小)元素
2)若它不是这组元素的最后一个(第一个)元素,则将它与这组元素的最后一个(第一个)元素交换
3)重复上述步骤,直到集合剩余一个元素
3.3 图解
3.4 代码实现
public static void SelectSort(int[] arr){
for(int i=0;i<arr.length;i++){
//无序[arr.length-i,arr.length-1]
//有序[0,arr.length-1-i]
int maxId = 0;
//查找整个无序区间的最大元素下标
for(int j=0;j<arr.length-i;j++){
if(arr[maxId] < arr[j]){
maxId=j;
}
}
//maxId记录无序区间最大元素的下标
//将maxId元素和无序区间最后一个元素交换
swap(arr,maxId,arr.length-i-1);
}
}
public static void swap(int[] arr, int i,int j) {
int temp = arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
3.5 优化(一次选择两个数)
//选择排序(一次选择两个数)
public static void SelectSortOP(int[] arr){
int begin = 0;
int end = arr.length-1;
/*[0,begin-1] 有序
[begin,end] 无序
[end+1,arr.length-1] 有序 */
while(begin<=end) {
int max = begin;
int min = begin;
for (int i = begin; i <= end; i++) {
if (arr[i] >= arr[max]) {
max = i;
}
if (arr[i] <= arr[min]) {
min = i;
}
}
/*最大的元素放到无序区间的最末尾
最小的元素放到无序区间的最开始*/
swap(arr, begin, min);
if(begin == max){
max = min;
}
swap(arr, end, max);
begin++;
end--;
}
}
public static void swap(int[] arr, int i,int j) {
int temp = arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
4 堆排序
4.1 基本思想
堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法,是选择排序的一种,通过堆来选择数据。
注意:排升序建大堆,排降序建小堆
4.2 算法步骤
1)建堆:从最后一个非叶子节点,一直到0,不断的向下调整
2)交换堆首(最大值)和堆尾
3)堆的大小减1,并调用Heapify(),调整无序部分
4)重复步骤2)、3),直到堆的大小为1
4.3 图解
4.4 代码实现
public static void HeapSort(int[] arr){
//建大堆,升序
CreateHeap(arr);
/*一次处理一个元素
无序[0,size-i-1]
有序[size-i,size-1]*/
for(int i=0;i<arr.length;i++){
//将最大的元素和无序区间的最后一个元素交换
swap(arr,0,arr.length-1-i);
//堆的性质被破坏(只有根),调整无序部分 长度为size-i-1
Heapify(arr, arr.length-i-1, 0);
}
}
public static void CreateHeap(int[] arr){
//从最后一个非叶子节点,一直到0,不断地向下调整
for(int i=(arr.length-2)/2; i>=0; i--){
Heapify(arr,arr.length,i);
}
}
public static void HeapifyNoR(int[] arr,int size,int index){
//判断是不是叶子节点
while(index*2+1<size) {
//不是叶子节点
int left = index*2+1;
int right = index*2+2;
int max = 0;
//有右孩子 并且 右孩子>左孩子
if(right<size && arr[left]<arr[right]){
max = right;
}else {
//1)没有右孩子
//2)有右孩子并且左孩子>右孩子
max = left;
}
//最大的孩子和根比较,如果根小,就交换
if (arr[max] > arr[index]) {
swap(arr, max, index);
}
//更换索引值,继续调整
index = max;
}
}
public static void Heapify(int[] arr,int size,int index){
int left = 2*index+1;
int right = 2*index+2;
if(left>=size){
return;
}
int max = 0;
if(right<size && arr[right]>arr[left]){
max=right;
}else {
max = left;
}
if(arr[index]>=arr[max]){
return;
}
swap(arr,index,max);
Heapify(arr,size,max);
}
public static void swap(int[] arr, int i,int j) {
int temp = arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
5 冒泡排序
5.1 基本思想
重复走访要排序的元素列,依次比较两个相邻的元素,如果顺序(例如从小到大)错误就把他们交换过来。
5.2 算法步骤
1)比较相邻的元素。如果第一个元素大于第二个元素,那么就交换他们
2)对每一对相邻元素做同样的操作,从开始第一对到结尾的最后一对。最后的元素会是最大的元素
3)针对所有元素重复上述步骤,除最后一个元素
4)持续每次对越来越少的元素重复上述步骤,知道没有任何一对元素需要比较
5.3 图解
5.4 代码实现
public static void BubbleSort(int[] arr){
for(int i=0;i<arr.length;i++){
boolean isSort = true;
for(int j=0;j<arr.length-i-1;j++){
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
isSort=false;
}
}
if(isSort==true){
break;
}
}
}
public static void swap(int[] arr, int i,int j) {
int temp = arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
6 快速排序
6.1 基本思想
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
6.2 算法步骤
1)选基准值
(1)选最左或最右的元素
(2)随机法(确定基准值后需要将基准值交换到最边上)
(3)三数取中法-区间最左边/最右边/中间,选值是中间的那个元素(确定基准值后需要将基准值交换到最边上)
2)做分割
遍历整个区间,和基准值做比较,并且交换。比基准值小的元素(可能包括等于)放在基准值左边,反之,放在右边
将区间按照基准值划分为左右两半部分的常见方式:
(1)Hover法
(2)挖坑法
(3)前后指针法
3)分治算法
直到size==0或size==1时退出
注意:如果选最左边元素为基准值,那么遍历优先从右边开始
6.3 图解
hover法
挖坑法
前后指针法
6.4 代码实现
public static void QuickSort(int[] arr){
QuickSortInner(arr,0,arr.length-1);
}
public static void QuickSortInner(int[] arr,int left,int right){
if(left>=right){
return;
}
//三数取中法确定基准值
int originIndex = ThreeElementOfCenter(arr,left,right);
swap(arr,originIndex,left);
int index = Partition3(arr,left,right);
QuickSortInner(arr,left,index-1);
QuickSortInner(arr,index+1,right);
}
//hover法
public static int Partition1(int[] arr, int left, int right) {
int key = arr[left];
int begin = left;
int end = right;
while(begin<end){
while(begin<end && arr[end]>=key){
end--;
}
while(begin<end && arr[begin]<=key){
begin++;
}
swap(arr,begin,end);
}
swap(arr,begin,left);
return begin;
}
//挖坑法
public static int Partition2(int[] arr, int left, int right){
int begin = left;
int end = right;
int key = arr[left];
while(begin<end){
while(begin<end && arr[end]>=key){
end--;
}
arr[begin] = arr[end];
while(begin<end && arr[begin]<=key){
begin++;
}
arr[end] = arr[begin];
}
arr[begin] = key;
return begin;
}
//前后指针法
public static int Partition3(int[] arr,int left,int right){
int pre = left;
int cur = left;
int key = arr[left];
while(cur<=right){
if(arr[cur]<=key){
swap(arr,pre,cur);
pre++;
}
cur++;
}
swap(arr,pre-1,left);
return pre-1;
}
//三数取中法
public static int ThreeElementOfCenter(int[] arr,int left,int right){
int mid = left +(right-left)/2;
if(arr[left]>arr[right]){
if(arr[left]<arr[mid]){
return left;
}else if(arr[mid]>arr[right]){
return mid;
}else{
return right;
}
}else{
if(arr[right]<arr[mid]){
return right;
}else if(arr[mid]>arr[left]){
return mid;
}else{
return left;
}
}
}
public static void swap(int[] arr, int i,int j) {
int temp = arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
7 归并排序
7.1 基本思想
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
7.2 图解
7.3 程序实现
public static void MergeSort(int[] arr){
int[] newArr = new int[arr.length];
MergeSortInner(arr,0,arr.length,newArr);
}
public static void MergeSortInner(int[] arr, int left, int right,int[] newArr) {
if(right==left+1){
return;
}
if(right<=left){
return;
}
int mid = left+(right-left)/2;
MergeSortInner(arr,left,mid,newArr);
MergeSortInner(arr,mid,right,newArr);
Merge(arr,left,mid,right,newArr);
}
public static void Merge(int[] arr, int left, int mid, int right,int[] newArr) {
int i=left;
int j=mid;
int x=0;
while(i<mid &&j<right){
if(arr[i]>arr[j]){
newArr[x++]=arr[j++];
}else{
newArr[x++]=arr[i++];
}
}
while(i<mid){
newArr[x++]=arr[i++];
}
while(j<right){
newArr[x++]=arr[j++];
}
x=0;
while(left<right){
arr[left++]=newArr[x++];
}
}
//非递归
public static void MergeSortNoR(int[] arr){
int[] newArr = new int[arr.length];
for(int i=1;i<arr.length;i=i*2){
for(int j=0;j<arr.length;j+=2*i){
int left = j;
int mid = left+i;
if(mid>=arr.length){
continue;
}
int right = mid+i;
if(right>arr.length){
right=arr.length;
}
Merge(arr,left,mid,right,newArr);
}
}
}
public static void swap(int[] arr, int i,int j) {
int temp = arr[i];
arr[i]=arr[j];
arr[j]=temp;
}