常见排序算法
归并排序(略过定义,把重点放在介绍如何一步步写出归并排序的思路)
第一步思考:
1、一开始:假设有一个自中间节点mid,在mid左右两边分别都已经有序的数组arr。如何将这个无序的数组变成有序呢?
思路
-
左半边的范围:0 – mid; 右半边的范围mid+1 – arr.length;
-
开始分别从左右两边的起始位置做比较,将其中较小的元素放在另外一个数组arrNew中(与原数组等长)
-
循环进行上一步操作,直到其中一个半边已经没有数据了,那么将另外一个半边的数据直接复制给新的数组arrNew
2、上述思路的实现
static void merge(int[] arr){
//作为左半边的起始标记
int i =0;
//中间节点mid
int mid = (arr.length-1)/2;
//作为右半边的起始节点
int j = mid+1;
//作为新数组的起始节点
int k = 0;
//新的数组arrNew
int[] arr2 = new int[arr.length];
//循环 - 比较 -复制
while(i<=mid&&j<arr.length){
if(arr[i]<=arr[j]){
arr2[k++] = arr[i++];
}else{
arr2[k++]=arr[j++];
}
}
//当其中一个半边没有数据,而另一个半边还有数据时,直接将余下的数据复制给arrNew
while(i<=mid) arr2[k++]=arr[i++];
while(j<arr.length) arr2[k++] = arr[j++];
}
第二步思考:
3、对于上述的改进,让上述变得更加灵活,不再规定是左半边和右半边,而是数组的某一部分,从下标left到下标为rightBound的部分数组元素,同时我们也需要一个mid标记作为右半边数据的起始下标**
//归并排序 排好的两半的升级版
//归并排序 排好的两半的升级版
static void merge(int[] arr,int left,int right,int rightBound){
//创建一个新的数组arrNew用来存储排好序的新数组
//这里要注意数组长度已经不是arr.length,而是数组中一部分的长度
int[] arrNew = new int[rightBound-left+1];
int i = left;
int j = right;
int mid = right-1;
int k = 0;
while(i<=mid && j<=rightBound){
if(arr[i]<=arr[j]) {
arrNew[k++]=arr[i++];
}else{
arrNew[k++]=arr[j++];
}
}
while(i<=mid) arrNew[k++] = arr[i++];
while(j<=rightBound) arrNew[k++] = arr[j++];
//将改变好的值赋值给原来的数组
for(int x = 0;x<rightBound-left+1;x++){
arr[left+x] = arrNew[x];
}
}
4、最终的归并排序算法
递归调用自己,并调用合并左右两边的算法即可
//归并排序
public static void sort(int[] arr,int left,int right){
//如果左边等于右边说明这个数组只有一个元素,则直接返回
if(left == right) return;
//分成两半
int mid = left + (right-left)/2; //防止int类型溢出
//左边排序
sort(arr,left,mid);
//右边排序
sort(arr,mid+1,right);
//合并左右两边
merge(arr,left,mid+1,right);
}
//归并(合并左右两边)
static void merge(int[] arr,int leftPtr,int rightPtr,int rightBound){
int mid = rightPtr-1;
int[] temp = new int[rightBound-leftPtr+1];
int i,j,k;
i = leftPtr;
j = rightPtr;
k = 0;
while(i<=mid && j<=rightBound){
temp[k++] = arr[i]<=arr[j]?arr[i++]:arr[j++];
}
while(i<=mid) temp[k++] = arr[i++];
while(j<=rightBound) temp[k++] = arr[j++];
for(int m = 0;m<temp.length;m++){
arr[leftPtr + m] = temp[m];
}
}
5、时间复杂度、空间复杂度分析
时间复杂度 :O(nlogn)
最好情况下:O(nlogn)
最坏情况下:O(n*logn)
空间复杂度:O(n)
稳定性:稳定
6、验证
因为排序次数少或者原始数据较少可能会产生偶然性,应该编写程序验证排序算法的正确性。
public class QuickSort {
public static void main(String[] args) {
// int[] arr = {1,5,9,8,7,4,6,2,3,10,1,2,6};
// quickSort(arr,0,arr.length-1);
// printArr(arr);
check();
}
//快速排序
static void sort(int[] arr,int leftBound,int rightBound) {
if (leftBound >= rightBound) return;
int mid = partition(arr, leftBound, rightBound);
sort(arr,leftBound,mid-1);
sort(arr,mid+1,rightBound);
}
//最开始的分成两半
static int partition(int[] arr, int leftBound, int rightBound) {
//设置一个轴 作为比较的初始值
int pivot = arr[rightBound];
int left = leftBound;
int right = rightBound-1;
while(left<=right){
while(left<=right && arr[left]<=pivot) left++;
while( left<=right && arr[right]>pivot) right--;
if(left<right) swap(arr,left,right);
}
swap(arr,left,rightBound);
//返回轴的位置
return left;
}
//交换两个数
static void swap(int[] arr ,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//打印数组
static void printArr(int[] array){
for(int i:array)
{
System.out.print(" "+i);
}
}
//验证排序算法的正确性
static void check(){
int[] arr = generateRandomArray();
//复制一个数组,一个用Java自带的Array.sort()方法,另外一个用自己写的选择排序
int[] arr2 = new int[arr.length];
//系统带的排序算法
Arrays.sort(arr);
//自己写的排序算法
sort(arr2,0,arr.length-1);
System.arraycopy(arr,0,arr2,0,arr.length);
boolean same = true;
for(int i = 0;i<arr2.length;i++){
if(arr[i]!=arr2[i])
same=false;
}
System.out.println( same == true ? "right":"wrong");
}
//产生一个随机数组
static int[] generateRandomArray(){
int[] array = new int[10000];
Random r = new Random();
for(int i=0;i<array.length;i++)
{
array[i]=r.nextInt(10000);
}
return array;
}
}