快速排序
插入排序
堆排序
快速排序
快速排序的核心算法思想是分而治之。在Java标准库中Arrays类的sort方法里源码使用的正是了优化后的快速排序掌握快排算法对于数据结构与算法入门极为重要。
原理
快速排序的核心思想是分治:选择数组中某个数作为基数,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数都比基数小,另外一部分的所有数都都比基数大,然后再按此方法对这两部分数据分别进行快速排序,循环递归,最终使整个数组变成有序。
基数选择
由于快速排序需要选定一个基数进行划分排序,关于基数选择有很多方式,而基数选择直接关系到快排的效率。事实上,选取基准元素应该遵循平衡子问题的原则:即使得划分后的两个子序列的长度尽量相同本篇以待排序数组首元素作为基数进行说明。本篇以最常见的使用数组首元素作为基数进行快速排序原理说明。
java代码实现
/**
* @description:双边循环法做partition的快速排序
*/
public class QKSort {
//=============================双边循环法做partition的快排=======================
//快速排序,输入 待排序数组 开始位置 结束位置
public static void quickSort(int[] arr,int start,int end) {
if(arr == null || start >= end) {return;}//递归结束条件
//得到基准元素的位置:已经划分好的数组 的 划分位置
int pivotIndex = partition(arr,start,end);
//分成两部分进行递归,pivot这个位置上的元素已经是有序的 每一次递归就解决一个元素
quickSort(arr,start,pivotIndex-1);
quickSort(arr,pivotIndex+1,end);
}
//双边循环法:带划分的数组 起始下标 结束下标
public static int partition(int[] arr, int start,int end) {
//取第一个位置上的数作为划分值 也可以随机选择一个数,就变成随机快排
int pivot = arr[start];
//两个指针
int left = start;
int right = end;
int temp=0;//用于交换俩个数
while(left != right) {
//先处理right指针:大于基准值就向左移动
while(left < right && arr[right] > pivot) {
right--;
}
//处理left指针 <=基准值才向右移动
while(left < right && arr[left] <= pivot) {
left++;
}
//交换left和right位置上的元素
if(left < right) {
temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
//最后交换基准元素和left位置上的元素[left和right已经重合了]
arr[start] = arr[left];
arr[left] = pivot;
return left;//返回划分的位置
}
//=============================双边循环法做partition的快排=======================
//=============================单边循环法做partition的快排=======================
public static void quickSort2(int[] arr,int start,int end) {
if(start >= end) {return;}
int pivotIndex = partition2(arr,start,end);
//分成两部分递归
quickSort2(arr,start,pivotIndex-1);
quickSort2(arr,pivotIndex+1,end);
}
public static int partition2(int[] arr,int start,int end) {
int pivot = arr[start];//基准值 也可随机选择一个
int left = start;
for(int i=start+1;i<=end;i++) {//从指针指向的下一个元素开始遍历
if(arr[i] < pivot) {
//left指针右移一位
left++;
//交换元素
swap(arr,left,i);
}
}
//每一轮的最后一步都是 将基准元素交换到left所指的位置 left位置上的元素来到数组的第一个位置
arr[start] = arr[left];
arr[left] = pivot;
return left;
}
//=============================单边循环法做partition的快排=======================
public static void printArray(int[] arr) {
// 打印数组
if (arr == null) return;
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// 交换数组中的两个数
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = new int[] {4,4,6,5,3,2,8,1};
printArray(arr);
quickSort2(arr,0,arr.length-1);
printArray(arr);
}
}
插入排序
算法思想
- 从第一个元素开始,该元素可以认为已经被排序
- 取出第一个未排序元素存放在临时变量temp中,在已经排序的元素序列中从后往前扫描,逐一比较
- 如果temp小于已排序元素,将该元素移到下个位置
- 重复步骤3,直到找到已排序的元素小于或者等于
代码实现
import java.util.Arrays;
/**
* 插入排序
* @author xcbeyond
*
*/
public class InsertSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int a[] ={13,15,37,89,60,39,12,109,56,72} ;
a = insertSort(a);
String s = Arrays.toString(a);
System.out.println(s);
}
public static int[] insertSort(int[] ary){
for(int i = 1;i < ary.length;i++){
int temp = ary[i];
int j;
for(j = i-1;j>=0;j--){
if(temp < ary[j]){
ary[j+1] = ary[j];
}
else
break; //找到插入位置
}
ary[j+1] = temp;
}
return ary;
}
}
堆排序
分为大根堆和小根堆,其中升序排列使用大根堆,降序排列使用小根堆。
使用到的数据结构:完全二叉树
注:完全二叉树是依次排列节点的二叉树;
完美二叉树:叶子节点都在同一层的完全二叉树;
堆排序:核心问题就是初始化大根堆,还有交换,调整大根堆的过程。
时间复杂度:O(nlogn)
算法流程:
-
调整大根堆
-
置换根结点:将树根元素和最末尾元素置换,确定最大值位置
-
调整新根结点位置:将新置换的树根结点调整到合适位置,保持大根堆属性
-
重复2,3步骤,直到遍历完整个二叉树
import java.util.Arrays;
import java.util.PriorityQueue;
/**
* @description:堆排序
* 1,先让整个数组都变成大根堆结构,建立堆的过程:
* 1)从上到下的方法:heapInsert() 时间复杂度为O(N*logN) ---只要求调整成大根堆 不要求元素有序
* 2)从下到上的方法heapify() 只有在给你多个数 组织成大[小]根堆,时间复杂度为O(N) --- 只要求调整成大根堆 不要求元素有序
* 2,把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,时间复杂度为O(N*logN)
* 3,堆的大小减小成0之后,排序完成。
*/
public class HeapSort {
//堆排序额外空间复杂度O(1)
public static void heapSort(int[] arr) {
if(arr == null || arr.length<2) return;
//从上到下的方法,时间复杂度为O(N*logN)
// for(int i=0;i<arr.length;i++) {//循环:O(N) //把数组所有数弄成大根堆:
// heapInsert(arr,i);//O(N*logN)
// }
for(int i =arr.length-1;i>=0 ; i-- ) {//把数组所有数弄成大根堆 O(N)
heapify(arr,i,arr.length);//收敛于 O(N)
}
int heapSize = arr.length;
//然后就是不断把第一个数交换到后面去
swap(arr,0,--heapSize);//全部交换到后面的位置 最大的是最后一个 次大的是倒数第二个 以此类推
//O(N*logN)
while(heapSize >0) { //O(N)
heapify(arr,0,heapSize);//O(logN)
swap(arr,0,--heapSize);// O(1)
}
}
//从最后一个位置调整到正确的位置: arr[index]是刚来的数,从下往上走
public static void heapInsert(int[] arr,int index) {
while(arr[index] >arr[(index-1) /2 ]) {
swap(arr,index,(index-1)/2);
index = (index-1)/2;
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
//从上到下 把一个数调整到正确的位置: arr[index]位置的数,能否往下移动
public static void heapify(int[] arr,int index,int heapSize) {
int left = 2*index +1;
while(left < heapSize) {
// 两个孩子中,谁的值大,把下标给largest
// 1)只有左孩子,left -> largest
// 2) 同时有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest
// 3) 同时有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largest
//int largest = (arr[left + 1] > arr[left]) && (left + 1 < heapSize) ? left+1: left;//必须先判断left+1会不会越界 才能使用arr[left+1]
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ?largest : index;
if(largest == index) {break;}
swap(arr,largest,index);
index = largest;//index来到与之交换元素的位置
left = index * 2 +1;
}
}
//for test
public static void printArray(int[] arr) {
for(int i=0;i<arr.length;i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
//for test:数组排序
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
//for test:生成随机数组
public static int[] generateRandomArray(int maxSize,int maxValue) {
int[] arr = new int[(int)(Math.random() * (maxSize +1))];
for(int i=0;i<arr.length;i++) {
arr[i] = (int)(Math.random() * (maxValue +1) - Math.random() * maxValue ) ;
}
return arr;
}
public static void main(String[] args) {
int[] test = {1,5,3,6,4,8};
//单次测试
printArray(test);
heapSort(test);
printArray(test);
}
}