注:这篇文章是数据结构之排序 一 (插入、选择)的续写。
排序算法实现
1.交换排序
基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。
特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
注:在本篇文章中会用到的交换方法。
public void swap(int[] array,int i,int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
(1)冒泡排序
实现思路:
冒泡排序的实现思路非常简单,只需要定义两个变量,一个用于遍历数组,一个用来和数组中的元素进行比较,排序即可。
代码实现:
public void bobbleSort(int[] array){
for (int i = 0; i < array.length-1; i++) {
boolean flag = false;
for (int j = 0; j < array.length-i-1; j++) {
if(array[j] > array[j+1]){
swap(array,j,j+1);
flag = true;
}
}
if(!flag){
break;
}
}
}
(2)快速排序
注:快速排序最常用 2 类,分别是 Hoare法、挖坑法。
基本思路:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
Hoare法
实现思路:
Hoare 法的实现类似于树的形式,运用到了递归的方法。
- 在每次排序中都需要找到一个基准。
- 寻找基准需要定义两个变量 left,right 分别指向数组的左端和右端。
- 定义一个变量 i 记录数组中 left 指向的下标。
- 数组中 left 指向的元素就是基准。
- 只要 right 指向的值大于 基准值,就 right- -。
- 只要 left 指向的值小于 基准值,就 left++。
- 4、5 条件均满足时就进行交换,直到 right 和 left 重合。
- 重合时就是将数组分为两半的基准。
代码实现
//定义快速排序方法
public void quickSortHoare(int[] array){
quickHoare(array,0,array.length-1);
}
private void quickHoare(int[] array,int start,int end){
//递归的终止条件
if(start >= end){
return;
}
//每次排序都需要找到一个基准
int pivot = partitionHoare(array,start,end);
//先判断左边
quickHoare(array,start,pivot-1);
//在判断右边
quickHoare(array,pivot+1,end);
}
//一个找到基准的方法
private int partitionHoare(int[] array,int left,int right){
int i = left;
int pivot = array[left];
while(left < right){
while(left < right && array[right] >= pivot){
right--;
}
while(left < right && array[left] <= pivot){
left++;
}
swap(array,left,right);
}
swap(array,left,i);
return left;
}
代码分析
上面的初步分析是整个逻辑运行的全部,在基准处分割后将数组分为左右两部分,类似于树的左右子树,因此运用了与树思路相似的递归方法。
挖坑法
实现思路
挖坑法的实现思路和 Hoare法 很相似,直接将 left 指向的元素记录下来,当满足条件时直接进行 left 和 right 下标的元素交换即可。
代码实现
public void quickSortDig(int[] array){
quickDig(array,0,array.length-1);
}
private void quickDig(int[] array,int start,int end){
//递归的终止条件
if(start >= end){
return;
}
//每次排序都需要找到一个基准
int pivot = partitionDig(array,start,end);
//先判断左边
quickDig(array,start,pivot-1);
//在判断右边
quickDig(array,pivot+1,end);
}
private int partitionDig(int[] array,int left,int right){
int pivot = array[left];
while(left < right){
while(left < right && array[right] >= pivot){
right--;
}
array[left] = array[right];
while(left < right && array[left] <= pivot){
left++;
}
array[right] = array[left];
}
array[left] = pivot;
return left;
}
(3)对快速排序的优化
通过上面的解释,我们不难发现,快速排序使用递归方法有可能会出现一个很大的问题,即,当数据处于比较有序的情况时,递归的深度就会非常的大,可能会出现栈溢出的情况,因此,在一定程度上需要进行优化,这里利用了三数取中法进行优化。
简单分析:
这里就不在对快速排序进行描述,直接对三数取中法进行描述。
在实现时,只需要先进行三数取中将元素调整后进入排序即可。
- 定义一个变量 mid 记录中间元素的结点
- 在定义 start 指向最左端,定义 end 指向最右端
情况有以下几种:
(1) 当 start < end 时
mid < start 时,start 是中间值
mid > end 时,end 中间值
最后就是 mid 值
(2) 当 start > end 时
- mid > start 时,start 是中间值。
- mid < end 时,end 时中间值。
- 最后就是 mid 值。
三数取中法代码如下
private static int findMidValue(int[] array,int start,int end){
int mid = (start + end)/2;
if(array[start] < array[end]){
if(array[mid] < array[start]){
return start;
}else if(array[mid] > array[end]){
return end;
}else{
return mid;
}
}else{
if(array[mid] > array[start]){
return start;
}else if(array[mid] < array[end]){
return end;
}else{
return mid;
}
}
}
(4)快速排序的非递归方法
实现思路:
- 实现一个栈,起始存放以基准为中心,左右两段数组的 第一个 和 最后个数组元素下标。
- 之后,只要栈的内容不为空,重复上述操作,直到栈空,实现数据有序。
代码实现:
//一个找到基准的方法
private int partition(int[] array,int left,int right){
int i = left;
int pivot = array[left];
while(left < right){
while(left < right && array[right] >= pivot){
right--;
}
while(left < right && array[left] <= pivot){
left++;
}
swap(array,left,right);
}
swap(array,left,i);
return left;
}
public void quickSortStack(int[] array){
Stack<Integer> stack = new Stack<>();
int start = 0;
int end = array.length-1;
int pivot = partition(array,start,end);
//判断左侧是否有两个元素
if(pivot > start+1){
stack.push(start);
stack.push(pivot-1);
}
//判断右边是否有两个元素
if(pivot < end-1){
stack.push(pivot+1);
stack.push(end);
}
while(!stack.isEmpty()){
end = stack.pop();
start = stack.pop();
pivot = partition(array,start,end);
//判断左侧是否有两个元素
if(pivot > start+1){
stack.push(start);
stack.push(pivot-1);
}
//判断右边是否有两个元素
if(pivot < end-1){
stack.push(pivot+1);
stack.push(end);
}
}
}
2.归并排序
基本思想:
是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
如图
实现思路:
观察上图,我们很容易可以联想到树的结构。确实,要实现归并排序的操作,同样要利用到递归的思想。
- 递归的思想很简单,当 left 和 right 相等时停止递归,每次递归先左后右。
- 重点是交换的思想,首先定义 s1 ,e1 ,s2,e2 分别记录左右两部分数组的起始和终点元素。
- 定义一个数组 tmpArr 用来存放交换后的数据。
代码实现:
private void mergeSortChild(int[] array,int left,int right){
//这里运用递归
//设置返回条件
if(left == right){
return;
}
int mid = (left + right) / 2;
mergeSortChild(array,left,mid);
mergeSortChild(array,mid+1,right);
merge(array,left,right,mid);
}
//定义交换方法
private void merge(int[] array,int left,int right,int mid){
int s1 = left;
int e1 = mid;
int s2 = mid+1;
int e2 = right;
//定义一个数组用于存放排序后的元素
int[] tmpArr = new int[right - left+1];
//定义 k 用来表示tmpArr的下标
int k = 0;
while(s1 <= e1 && s2 <= e2){
if(array[s1] <= array[s2]){
tmpArr[k++] = array[s1++];
}else{
tmpArr[k++] = array[s2++];
}
}
while(s1 <= e1){
tmpArr[k++] = array[s1++];
}
while(s2 <= e2){
tmpArr[k++] = array[s2++];
}
for (int i = 0; i < k; i++) {
array[i + left] = tmpArr[i];
}
}
非递归方法:
public void mergeSort2(int[] array){
int gap = 1;
while(gap < array.length){
for (int i = 0; i < array.length; i += gap*2) {
int left = i;
int mid = left + gap - 1;
int right = mid + gap;
if(mid >= array.length){
mid = array.length - 1;
}
if(right >= array.length){
right = array.length - 1;
}
merge(array,left,right,mid);
}
gap *= 2;
}
}