/**
* 快排概念:
* 快排也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的;在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,
* 比它小的元素移动到数组的另一边,从而将数组拆成两个部分,这种思路叫做分治法
*
* 时间复杂度:
* 原数组在分支法的思想下,没一轮都会被拆分为两部分,所以遍历的轮数为logn,每一轮的比较和交换需要把数组全部遍历一遍,
* 这个时间复杂度是O(n),所以总的平均时间复杂度是O(nlogn)
* 但是如果将原本逆序的数列排序成顺序数列,整个数列并没有被分成两半,每一轮都只确定来基准元素的位置,数列的第一个元素要么是最大值要么是最小值,
* 无法发挥分支法的优势,这个时候快速排序需要进行n轮,时间复杂度退化为O(n平方)。
* 解决思路是:随机选择一个元素作为基准元素
*
*/
/**
* 递归的方式
* @param arr
* @param startIndex
* @param endIndex
*/
public static void quickSort(int[] arr,int startIndex,int endIndex){
if(arr == null || startIndex >= endIndex){
return;
}
//得到基准元素位置
int pivotIndex = partition(arr,startIndex,endIndex);
//根据基准元素位置分成两部分进行递归
quickSort(arr,startIndex,pivotIndex - 1);
quickSort(arr,pivotIndex + 1,endIndex);
}
/**
* 循环方式
* 引入一个存储Map类型元素的栈,用于存储每一次交换时的起始下标和结束下标
* 每一次循环,都会让栈顶元素出栈,通过partition方法方法进行分支,并且按照基准元素的位置分成左右两部分,左右两部分再分别入栈。
* 当栈为空时,说明排序已经完毕,推出循环
*/
public static void quickSort2(int[] arr,int startIndex,int endIndex){
Stack<Map<String,Integer>> stack = new Stack<>();
Map<String,Integer> root = new HashMap<>();
root.put("startIndex",startIndex);
root.put("endIndex",endIndex);
stack.push(root);
while (!stack.isEmpty()){
Map<String,Integer> item = stack.pop();
int pivotIndex = partition(arr,item.get("startIndex"),item.get("endIndex"));
if(item.get("startIndex")<pivotIndex - 1){
Map<String,Integer> left = new HashMap<>();
left.put("startIndex",item.get("startIndex"));
left.put("endIndex",pivotIndex - 1);
stack.push(left);
}
if(pivotIndex + 1 < item.get("endIndex")){
Map<String,Integer> right = new HashMap<>();
right.put("startIndex",pivotIndex + 1);
right.put("endIndex",item.get("endIndex"));
stack.push(right);
}
}
}
/**
* 选择基准元素位置:(单边循环法)
* 首先选定基准元素 pivot,同时设置一个mark指针指向数列起始位置,这个mark指针代表小于基准元素的区域边界
* 接下来从基准元素的下一个位置开始遍历数组
* 如果遍历到的元素大于基准元素,则继续往下遍历
* 如果遍历到的元素小于基准元素,需要做两件事情。
* 第一:将mark元素向右移动1位,因为小于pivot基准元素的区域边界增大了1
* 第二:将遍历到的最新的元素和mark位置指针所在的元素互换位置,因为最新遍历的元素归属于小于pivot的区域
* @param arr
* @param startIndex
* @param endIndex
* @return
*/
public static int partition(int[] arr,int startIndex,int endIndex){
int pivot = arr[startIndex];
int mark = startIndex;
for(int i = startIndex + 1;i <= endIndex ;i++){
if(arr[i]<pivot){
mark ++;
int temp = arr[mark];
arr[mark] = arr[i];
arr[i] = temp;
}
}
arr[startIndex] = arr[mark];
arr[mark] = pivot;
return mark;
}
/**
* 归并排序
* 分治、递归思想
* 要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分进行排序,然后将排序好的两部分合并
* 最好、最坏、平均时间复杂度都为 O(nlogn),需要空间复杂度为O(n),是稳定的非原地算法
* @param data
* @param p
* @param r
*/
public static void mergeSort(int[] data,int p,int r){
//终止条件
if(p>=r) return;
//防止 p和r过大,(p+r)溢出
int q = p +(r- p)/2;
//分治递归
mergeSort(data,p,q);
mergeSort(data,q + 1,r);
// 将A[p...q]和A[q+1...r]合并为A[p...r]
merge(data,p,q,r);
}
private static void merge(int[] data,int p,int q,int r){
int i= p;
int j = q +1;
int k = 0;
int[] temp = new int[r-p + 1];
while (i<=q && j<=r){
if(data[i]<=data[j]){
temp[k++] = data[i++];
}else {
temp[k++] = data[j++];
}
}
// 判断哪个子数组中有剩余的数据
int start = i;
int end = q;
if(j<=r){
start = j;
end = r;
}
// 将剩余的数据拷贝到临时数组tmp
while (start <= end){
temp[k++]=data[start ++];
}
//将临时变量拷贝回a[p..r]
for (i=0;i<=r-p;i++){
data[p+i] = temp[i];
}
}
/**
* 冒泡排序,基本优化
* 冒泡排序思想:把相邻两个元素两两比较,当一个元素大于右侧相邻元素时,交换它们的位置,当一个元素小于或等于右侧相邻元素时,位置不变。一次冒泡会让至少一个元素移动到它应该在的位置,重复n次,就完成了n个数据的排序工作
* 为原地稳定算法,时间复杂度为n²,最好时间复杂度为n,平均和最坏情况为n²
* 采用双循环进行排序,外部循环控制所有回合,内部循环实现每一轮的冒泡处理,先进行元素比较,再进行元素交换
*
* @param arr
*/
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
//有序标记,每一轮的初始致都为true
boolean isSorted = true;
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
//因为有元素交换,所以是无序的
isSorted = false;
}
}
if (isSorted) {
break;
}
}
}
/**
* 冒泡排序优化
* sortBorder是无序数列的边界,在每一轮排序过程中,处于sortBorder之后的元素不需要进行比较了,肯定是有序的
* @param arr
*/
public static void bubbleSort2(int[] arr) {
//记录最后一次交换的位置
int lastExchangeIndex = 0;
//无序数列的边界,每次比较只需要比较到此为止
int sortBorder = arr.length - 1;
for (int i = 0; i < arr.length; i++) {
//有序标记,每一轮的初始致都为true
boolean isSorted = true;
for (int j = 0; j < sortBorder; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
//因为有元素交换,所以是无序的
isSorted = false;
//更新为最后一次交换元素的位置
lastExchangeIndex = j;
}
}
sortBorder = lastExchangeIndex;
if (isSorted) {
break;
}
}
}
/**
* 插入排序
* 首先我们将数组中数据分为两个区间,已排序和未排序区间。初始化时已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序算法中的元素,在已排序算法中找到合适的插入位置将其插入
* 原地稳定算法,时间复杂度为O(n2),但它赋值次数比冒泡的少(插入赋值一次,冒泡3次),所以效率比冒泡的高
* @param data
*/
public static void insertSort(int[] data){
if(data == null || data.length <= 1) return;
for (int i=1;i<data.length;i++){
int val = data[i];
int j = i - 1;
//查找插入的位置
for(; j>=0 ;j--){
if(data[j]>val){
//数据移动
data[j+1] = data[j];
}else {
break;
}
}
//找到位置插入
data [j + 1] = val;
}
}
public static void main(String[] args) {
int[] data ={6,11,3,9,8};
quickSort(data);
System.out.println("快排结果为"+ Arrays.toString(data));
data =new int[]{6,11,3,9,8};
mergeSort(data,0,data.length-1);
System.out.println("归并排序结果为"+ Arrays.toString(data));
data =new int[]{6,11,3,9,8};
bubbleSort(data);
System.out.println("冒泡排序结果为"+ Arrays.toString(data));
data =new int[]{6,11,3,9,8};
insertSort(data);
System.out.println("插入排序结果为"+ Arrays.toString(data));
}