选择排序
- 基本思想:遍历数组,找到最小的元素和第一个元素(nums[0])交换。次小的和第二个元素交换,也就是每次找到未排序部分中的最小值放在适当的位置。
- 复杂度分析:长度为N的数组,比较次数:N^2/2 交换次数:N 时间复杂度:N^2
- 特点:运行时间和输入无关,每次都需遍历未排序部分找到最小值;数据的移动交换次数是最少的;
- 代码:
public void select_sort(int[] nums){
int len = nums.length;
int k;
for(int i=0;i<len-1;i++){
k = i;//标记最小值的下标
for(int j=i+1;j<len;j++){
if(nums[j] < nums[k])
k = j;
}
if(k != i){
int tmp = nums[i];
nums[i] = nums[k];
nums[k] = tmp;
}
}
}
- 改进:简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。
public void select_sort_next(int[] nums){
int len = nums.length;
int min;int max;
for(int i=0,j=len-1;i<j;i++,j--){
min = i;//标记最小值的下标
max = j;
for(int k=i;k<=j;k++){
if(nums[k] < nums[min])
min = k;
if(nums[k] > nums[max])
max = k;
}
if(min != i){
int tmp = nums[i];
nums[i] = nums[min];
nums[min] = tmp;
}
if(max != j){
int tmp = nums[j];
nums[j] = nums[max];
nums[max] = tmp;
}
}
}
- 插入排序
- 基本思想:扑克牌,拿起一张牌插入左手中已排序的部分,可以从右向左和手中的牌依次比较,直到找到一个小于他的(升序排列)。
- 复杂度分析:长度为N的数组,最坏情况比较次数:N^2/2 交换次数:N^2/2 时间复杂度:介于N和N^2之间
- 特点:运行时间和输入有关,若输入是一个已排序的数组,能很快发现,运行时间线性,对于所有元素都相等的数组,时间也是线性的;特别适合部分有序数组的排序以及小规模数组,所以在归并和快速排序中,数组变小后可以使用插入排序提高效率;是稳定的;
- 代码:
public void Insertion_sort(int[] nums){
int len = nums.length;
for(int i=1;i<len;i++){//i刚拿起的未排序的扑克牌
int tmp = nums[i];
int j = i-1;
while(j >= 0 && tmp < nums[j]){//j从右向左将手中的牌依次和i比较
nums[j+1] = nums[j];
j--;
}
nums[j+1] = tmp;
}
}
- 冒泡排序
- 基本思想:(升序排列)最大值向下沉,相邻的元素依次比较。
- 复杂度分析:长度为N的数组,比较次数:N^2/2 交换次数:N^2/2 时间复杂度:N^2
- 特点:运行时间和输入无关;
- 代码:
public void Bubble_sort(int[] nums){
int len = nums.length;
for(int i=len-1;i>0;i--){//i最大值存放的位置
for(int j=0;j<i;j++){
if(nums[j] >= nums[j+1]){
int tmp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = tmp;
}
}
}
}
- 改进:对冒泡排序常见的改进方法是加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。所以可以:设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。
public void Bubble_sort_next(int[] nums){
int len = nums.length;
for(int i=len-1;i>0;){
int tmp_pos = 0;
for(int j=0;j<i;j++){
if(nums[j] >= nums[j+1]){
tmp_pos = j;
int tmp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = tmp;
}//pos之后都是排好序的了
}
i = tmp_pos;
}
}
- 归并排序
- 基本思想:将两个已排序的数组归并为一个有序数组。
- 复杂度分析:长度为N的数组,时间复杂度:NlogN
- 特点:稳定排序,需要额外的空间,空间复杂度O(N)
- 代码:
public void merge(int[] nums,int start,int end,int mid){
//额外的空
int[] arr = new int[end-start+1];
int i = start;
int j = mid+1;
int k = 0;
while(i<=mid && j<=end){
if(nums[i] < nums[j])
arr[k++] = nums[i++];
else
arr[k++] = nums[j++];
}
while(i<=mid)
arr[k++] = nums[i++];
while(j<=end)
arr[k++] = nums[j++];
for(i=start;i<=end;i++)
nums[i] = arr[i-start];
}
//递归版本
public void Merge_sort(int[] nums,int start,int end){
if(start >= end)
return;
int mid = start + (end-start)/2;
Merge_sort(nums,start,mid);
Merge_sort(nums,mid+1,end);
merge(nums,start,end,mid);
}
- 快速排序
- 基本思想:将数组切分为两部分,两部分整体有序,内部无序,之后递归切分即可。
- 复杂度分析:长度为N的数组,时间复杂度:NlogN
- 特点:简单、高效、适用于各种不同的输入数据
- 代码:
public int partition(int[] nums,int start,int end){
int x = nums[start];//哨兵节点
int i = start+1;
int j = end;
while(i <= j){
while(i <= j && nums[i] < x)
i++;
while(i <= j && nums[j] >= x)
j--;
if(i > j)
break;
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
nums[start] = nums[j];
nums[j] = x;
return j;
}
//递归版本
public void Quick_sort(int[] nums,int start,int end){
if(start >= end)
return;
int index = partition(nums,start,end);
Quick_sort(nums,start,index-1);
Quick_sort(nums,index+1,end);
}
- 改进:当数组比较小时,就可以使用插入排序:代码如下
public void Quick_sort_next(int[] nums,int start,int end,int k){
if(end <= start+k){
Insertion(nums,start,end);
return;
}
int index = partition(nums,start,end);
Quick_sort(nums,start,index-1);
Quick_sort(nums,index+1,end);
}
- 改进:当数组中包含大量重复元素时,可以将数组切分为三部分:小于、等于和大于x,从而避免对元素全部重复的子数组的递归排序;
public void Quick_sort_improve(int[] nums,int start,int end){
//递归结束的条件
if(start >= end)
return;
int x = nums[start];
int i = start +1;
int lt = start;//存放下一个小于x的值,目前指向等于x的值,lt与i之间的元素等于x
int gt = end;//存放下一个大于x的值,i与gt之间的是未排序的
while(i <= gt){
if(nums[i] < x)
exchange(nums,i++,lt++);//lt原来指向等于x的值,i小于x的值,交换后均向后移动
else if(nums[i] > x)
exchange(nums,i,gt--);//gt原来指向的值大小不明确,所以交换后i不变
else
i++;//等于x,i++即可
}
//此时,start--lt-1,小于x,lt--gt等于x,gt+1--end大于x,递归对两部分排序即可
Quick_sort_improve(nums,start,lt-1);
Quick_sort_improve(nums,gt+1,end);
}
public static void main(String[] args){
int[] nums = {1,9,11,-5,-2,0,-5,7,8,1,20,11,-11,32,-9,56,1,1,1,1,1,-11};
//int[] nums = {1,9,11,14,20};
new Test().Quick_sort_improve(nums,0,nums.length-1);
for(int i=0;i<nums.length;i++)
System.out.print(nums[i]+" ");
}
/*基本思想:
*当数组中包含大量重复元素时,利用此改进的快速排序,可达到线性的时间复杂度
*数组分为三部分,小于x,等于x,大于x*/
public void exchange(int[] nums,int i,int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
- 还有一种改进就是:使用子数组的一小部分元素的中位数来切分数组,也就是选择更好的x,使得切分后左右两部分尽可能均匀,代价是需要计算中位数;
- 参考:《算法》 八大排序算法
- 基本思想:遍历数组,找到最小的元素和第一个元素(nums[0])交换。次小的和第二个元素交换,也就是每次找到未排序部分中的最小值放在适当的位置。
- 复杂度分析:长度为N的数组,比较次数:N^2/2 交换次数:N 时间复杂度:N^2
- 特点:运行时间和输入无关,每次都需遍历未排序部分找到最小值;数据的移动交换次数是最少的;
- 代码:
public void select_sort(int[] nums){ int len = nums.length; int k; for(int i=0;i<len-1;i++){ k = i;//标记最小值的下标 for(int j=i+1;j<len;j++){ if(nums[j] < nums[k]) k = j; } if(k != i){ int tmp = nums[i]; nums[i] = nums[k]; nums[k] = tmp; } } }
- 改进:简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。
public void select_sort_next(int[] nums){ int len = nums.length; int min;int max; for(int i=0,j=len-1;i<j;i++,j--){ min = i;//标记最小值的下标 max = j; for(int k=i;k<=j;k++){ if(nums[k] < nums[min]) min = k; if(nums[k] > nums[max]) max = k; } if(min != i){ int tmp = nums[i]; nums[i] = nums[min]; nums[min] = tmp; } if(max != j){ int tmp = nums[j]; nums[j] = nums[max]; nums[max] = tmp; } } }
- 基本思想:扑克牌,拿起一张牌插入左手中已排序的部分,可以从右向左和手中的牌依次比较,直到找到一个小于他的(升序排列)。
- 复杂度分析:长度为N的数组,最坏情况比较次数:N^2/2 交换次数:N^2/2 时间复杂度:介于N和N^2之间
- 特点:运行时间和输入有关,若输入是一个已排序的数组,能很快发现,运行时间线性,对于所有元素都相等的数组,时间也是线性的;特别适合部分有序数组的排序以及小规模数组,所以在归并和快速排序中,数组变小后可以使用插入排序提高效率;是稳定的;
- 代码:
public void Insertion_sort(int[] nums){ int len = nums.length; for(int i=1;i<len;i++){//i刚拿起的未排序的扑克牌 int tmp = nums[i]; int j = i-1; while(j >= 0 && tmp < nums[j]){//j从右向左将手中的牌依次和i比较 nums[j+1] = nums[j]; j--; } nums[j+1] = tmp; } }
- 基本思想:(升序排列)最大值向下沉,相邻的元素依次比较。
- 复杂度分析:长度为N的数组,比较次数:N^2/2 交换次数:N^2/2 时间复杂度:N^2
- 特点:运行时间和输入无关;
- 代码:
public void Bubble_sort(int[] nums){ int len = nums.length; for(int i=len-1;i>0;i--){//i最大值存放的位置 for(int j=0;j<i;j++){ if(nums[j] >= nums[j+1]){ int tmp = nums[j]; nums[j] = nums[j+1]; nums[j+1] = tmp; } } } }
- 改进:对冒泡排序常见的改进方法是加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。所以可以:设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。
public void Bubble_sort_next(int[] nums){ int len = nums.length; for(int i=len-1;i>0;){ int tmp_pos = 0; for(int j=0;j<i;j++){ if(nums[j] >= nums[j+1]){ tmp_pos = j; int tmp = nums[j]; nums[j] = nums[j+1]; nums[j+1] = tmp; }//pos之后都是排好序的了 } i = tmp_pos; } }
- 基本思想:将两个已排序的数组归并为一个有序数组。
- 复杂度分析:长度为N的数组,时间复杂度:NlogN
- 特点:稳定排序,需要额外的空间,空间复杂度O(N)
- 代码:
public void merge(int[] nums,int start,int end,int mid){ //额外的空 int[] arr = new int[end-start+1]; int i = start; int j = mid+1; int k = 0; while(i<=mid && j<=end){ if(nums[i] < nums[j]) arr[k++] = nums[i++]; else arr[k++] = nums[j++]; } while(i<=mid) arr[k++] = nums[i++]; while(j<=end) arr[k++] = nums[j++]; for(i=start;i<=end;i++) nums[i] = arr[i-start]; } //递归版本 public void Merge_sort(int[] nums,int start,int end){ if(start >= end) return; int mid = start + (end-start)/2; Merge_sort(nums,start,mid); Merge_sort(nums,mid+1,end); merge(nums,start,end,mid); }
- 基本思想:将数组切分为两部分,两部分整体有序,内部无序,之后递归切分即可。
- 复杂度分析:长度为N的数组,时间复杂度:NlogN
- 特点:简单、高效、适用于各种不同的输入数据
- 代码:
public int partition(int[] nums,int start,int end){ int x = nums[start];//哨兵节点 int i = start+1; int j = end; while(i <= j){ while(i <= j && nums[i] < x) i++; while(i <= j && nums[j] >= x) j--; if(i > j) break; int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } nums[start] = nums[j]; nums[j] = x; return j; } //递归版本 public void Quick_sort(int[] nums,int start,int end){ if(start >= end) return; int index = partition(nums,start,end); Quick_sort(nums,start,index-1); Quick_sort(nums,index+1,end); }
- 改进:当数组比较小时,就可以使用插入排序:代码如下
public void Quick_sort_next(int[] nums,int start,int end,int k){ if(end <= start+k){ Insertion(nums,start,end); return; } int index = partition(nums,start,end); Quick_sort(nums,start,index-1); Quick_sort(nums,index+1,end); }
- 改进:当数组中包含大量重复元素时,可以将数组切分为三部分:小于、等于和大于x,从而避免对元素全部重复的子数组的递归排序;
public void Quick_sort_improve(int[] nums,int start,int end){ //递归结束的条件 if(start >= end) return; int x = nums[start]; int i = start +1; int lt = start;//存放下一个小于x的值,目前指向等于x的值,lt与i之间的元素等于x int gt = end;//存放下一个大于x的值,i与gt之间的是未排序的 while(i <= gt){ if(nums[i] < x) exchange(nums,i++,lt++);//lt原来指向等于x的值,i小于x的值,交换后均向后移动 else if(nums[i] > x) exchange(nums,i,gt--);//gt原来指向的值大小不明确,所以交换后i不变 else i++;//等于x,i++即可 } //此时,start--lt-1,小于x,lt--gt等于x,gt+1--end大于x,递归对两部分排序即可 Quick_sort_improve(nums,start,lt-1); Quick_sort_improve(nums,gt+1,end); } public static void main(String[] args){ int[] nums = {1,9,11,-5,-2,0,-5,7,8,1,20,11,-11,32,-9,56,1,1,1,1,1,-11}; //int[] nums = {1,9,11,14,20}; new Test().Quick_sort_improve(nums,0,nums.length-1); for(int i=0;i<nums.length;i++) System.out.print(nums[i]+" "); }
/*基本思想: *当数组中包含大量重复元素时,利用此改进的快速排序,可达到线性的时间复杂度 *数组分为三部分,小于x,等于x,大于x*/ public void exchange(int[] nums,int i,int j){ int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
- 还有一种改进就是:使用子数组的一小部分元素的中位数来切分数组,也就是选择更好的x,使得切分后左右两部分尽可能均匀,代价是需要计算中位数;
- 参考:《算法》 八大排序算法