欢迎移步博主小站:白亮吖雅黑丫の小站
数据结构之排序算法的学习
冒泡排序
简介
冒泡排序,又名起泡排序,顾名思义,就如同气泡从水中不断上浮一样。
其算法思想为元素从数列头开始依次往数列尾进行比较,第一遍将最大的元素放置于数列尾的有序区,接着再从数列头的无序区中找到第二大的元素置于数列尾的有序区,如此进行下去,直至数列中所有元素有序。
算法复杂度
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
代码实现
- Java代码实现:
//冒泡排序
public static void sort(int[] array) {
//记录最后一次交换的位置
int lastExchangeIndex = 0;
//无序数列的边界,每次只需比较到这里为止
int sortBorder = array.length - 1;
for (int i = 0; i < array.length - 1; ++i) {
boolean flag = true;
for (int j = 0; j < sortBorder; ++j) {
if (array[j] > array[j + 1]) {
array[j] = array[j] ^ array[j + 1];
array[j + 1] = array[j] ^ array[j + 1];
array[j] = array[j] ^ array[j + 1];
//更新最后一次交换的位置
lastExchangeIndex = j;
flag = false;
}
}
//更新边界
sortBorder = lastExchangeIndex;
if (flag)
break;
}
}
冒泡改进之鸡尾酒排序
简介
由于冒泡排序每一轮都是从左至右
来比较元素,只能单向
的进行位置交换,在碰见{2,3,4,5,6,7,8,1}这样的数组时,使用冒泡排序还需进行7轮排序,效率比较低下。而我们当然是不希望这种情况的出现啦,由此鸡尾酒排序算法
便诞生了!
鸡尾酒排序算法和冒泡排序的不同之处在于遍历算法由单向遍历改为双向遍历,这样能避免出现前端基本有序的数列遍历时效率低下的问题,那么双向遍历究竟是怎样的呢? 下面将为大家详细说明。
鸡尾酒排序第一轮和冒泡排序一样,也是从左至右比较元素,而到了第二轮时,则反过来从右至左开始比较元素,这样往复,直至数列全部有序。
鸡尾酒排序的过程就像钟摆一样,第一轮从左至右,第二轮从右往左,第三轮再从左至右······
像{2,3,4,5,6,7,8,1}这样的数列,使用鸡尾酒排序只用3轮遍历就已经解决了!
复杂度
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
代码实现
- Java代码实现
//鸡尾酒排序
public static void jwSort(int[] array) {
for (int i = 0; i < array.length / 2; ++i) {
boolean flag = true;
//奇数轮,从左往右
for (int j = i; j < array.length - 1; ++j) {
if (array[j] > array[j + 1]) {
array[j] = array[j] ^ array[j + 1];
array[j + 1] = array[j] ^ array[j + 1];
array[j] = array[j] ^ array[j + 1];
flag = false;
}
}
if (flag)
break;
//重置标记
flag = true;
//偶数轮,从右往左
for (int j = array.length - 1; j > i; --j) {
if (array[j] < array[j - 1]) {
array[j] = array[j] ^ array[j - 1];
array[j - 1] = array[j] ^ array[j - 1];
array[j] = array[j] ^ array[j - 1];
flag = false;
}
}
if (flag)
break;
}
}
补充说明
鸡尾酒排序的优点是能够在特定的条件下,减少排序的回合数;而缺点也很明显,就是代码量几乎增加了1倍。
至于它能发挥出优势的场景,是大部分元素已经有序的情况。
快速排序
上面介绍的算法时间复杂度都为O(n²),那么有没有比它们更快的算法呢?
现在我们介绍一种时间复杂度为O(nlogn)的算法—快速排序
简介
快速排序之所以快,是因为它使用了分治法,将大问题划分为小问题,分而治之。
快速排序和冒泡排序一样,也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。
不同的是,冒泡排序在每一轮中只把一个元素冒泡到数列的一端,而快速排序则在每一轮挑选一个基准元素,并让其他比它打的元素移动到数列的一边,比它小的元素移动到数列的另一边,从而吧数列拆解成两个部分,如此拆分下去,不断将复杂问题简单化,这种思路就叫做分治法。
复杂度
时间复杂度:O(nlogn)
空间复杂度:O(logn)
代码实现
1.双边循环法
//快排辅助函数 双边循环
public static int partition(int[] array, int startIndex, int lastIndex) {
//取第一个元素为基准元素
int pivot = array[startIndex];
int left = startIndex;
int right = lastIndex;
while (left != right) {
//控制right指针比较并左移
while (left < right && array[right] > pivot) {
--right;
}
//控制left指针比较并右移
while (left < right && array[left] <= pivot) {
++left;
}
//交换left和right指针所指向元素
if (left < right) {
array[left] = array[left] ^ array[right];
array[right] = array[left] ^ array[right];
array[left] = array[left] ^ array[right];
}
}
//pivot和指针重合点交换
array[startIndex] = array[left];
array[left] = pivot;
return left;
}
2.单边循环法
//快排辅助函数 单边循环
public static int singlePartition(int[] array, int startIndex, int lastIndex){
//取第一个元素为基准元素
int pivot = array[startIndex];
int mark = startIndex;
for (int i = startIndex + 1;i<=lastIndex;++i){
if (array[i] < pivot){
mark++;
int p = array[mark];
array[mark] = array[i];
array[i] = p;
//请勿用下列方式进行两值得交换,此处会发生i==mark ,使得异或值变为零
// array[mark] = array[i]^array[mark];
// array[i] = array[i]^array[mark];
// array[mark] = array[i]^array[mark];
}
}
array[startIndex] = array[mark];
array[mark] = pivot;
return mark;
}
- 快排递归实现
//快排
public static void quickSort(int[] array, int startIndex, int lastIndex) {
//递归结束条件
if (startIndex >= lastIndex)
return;
//得到基准元素
int pivotIndex = singlePartition(array, startIndex, lastIndex);
//根据基准元素,分成2部分进行排序
quickSort(array, startIndex, pivotIndex - 1);
quickSort(array, pivotIndex + 1, lastIndex);
}
- 快排非递归实现
//快排非递归实现
public static void NoQuickSort(int[] array, int startIndex, int lastIndex){
//用栈实现非递归快排
Stack<Map<String,Integer>> quickSortStack = new Stack<Map<String, Integer>>();
//整个数列的起止下标入栈
Map rootParam = new HashMap();
rootParam.put("startIndex",startIndex);
rootParam.put("endIndex",lastIndex);
quickSortStack.push(rootParam);
//栈非空
while(!quickSortStack.isEmpty()){
//栈顶元素出栈
Map<String,Integer> param = quickSortStack.pop();
//得到基准元素位置
int pivotIndex = singlePartition(array,param.get("startIndex"),param.get("endIndex"));
//根据基准元素将数列分成2部分
if(param.get("startIndex") < pivotIndex - 1){
Map<String,Integer> leftParam = new HashMap<String,Integer>();
leftParam.put("startIndex",param.get("startIndex"));
leftParam.put("endIndex",pivotIndex - 1);
quickSortStack.push(leftParam);
}
if(pivotIndex + 1 < param.get("endIndex")){
Map<String,Integer> rightParam = new HashMap<String,Integer>();
rightParam.put("startIndex",pivotIndex + 1);
rightParam.put("endIndex",param.get("endIndex"));
quickSortStack.push(rightParam);
}
}
}
堆排序
简介
堆排序是基于二叉堆实现的。
二叉堆的特性
1.最大堆的堆顶是整个堆中的最大元素
2.最小堆的堆顶是整个堆中的最小元素
堆排序就是不断去构建最大堆/最小堆去实现的
实现步骤:
1.把无序数列构建成二叉堆。
2.循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶
复杂度
时间复杂度:O(nlogn)
空间复杂度:O(1)
代码实现
//堆排序 升序
public static void heapSort(int[] array){
//把无序数组构建成大顶堆
for (int i = (array.length - 2) / 2;i>=0;--i){
downAdjust(array,i,array.length);
}
//输出构建好的大顶堆
// System.out.println(Arrays.toString(array));
//循环删除堆顶元素,调整堆产生新的堆顶
for (int i = array.length - 1;i>0;--i){
//最后一个元素和第一个元素交换
array[i] = array[0]^array[i];
array[0] = array[0]^array[i];
array[i] = array[0]^array[i];
//执行下沉操作
downAdjust(array,0,i);
}
}
//堆排辅助函数,下沉操作
public static void downAdjust(int[] array, int parentIndex, int length){
//temp保存根结点信息
int temp = array[parentIndex];
int childIndex = parentIndex * 2 + 1;
//大顶堆
while (childIndex < length){
//如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子
if (childIndex + 1 < length && array[childIndex + 1] > array[childIndex]){
childIndex++;
}
//如果父节点大于任何一个孩子的值,则直接跳出
if (temp >= array[childIndex])
break;
//无需赋值,单向交换
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = parentIndex * 2 + 1;
}
//最后赋值
array[parentIndex] = temp;
}
计数排序
简介
计数排序是一种线性时间复杂度的算法。采用了空间换时间的方式来实现排序!
复杂度
时间复杂度:O(n+m)
空间复杂度:O(m)
代码实现
//计数排序
public static int[] countSort(int[] array){
int max = array[0];
int min = array[0];
//得出数组最大值与最小值
for (int i = 1; i<array.length;++i){
if (array[i]>max){
max = array[i];
}
if (array[i]<min) {
min = array[i];
}
}
//创建数组
int[] countArray = new int[max - min + 1];
//计数
for (int i = 0; i<array.length;++i ){
countArray[array[i]-min]++;
}
//统计数组做变形,后面的元素等于前面的元素相加
for (int i = 1; i<countArray.length;++i){
countArray[i] += countArray[i - 1];
}
//倒序遍历原始数组,从统计数组中找到正确的位置,输出到结果数组
int[] sortedArray = new int[array.length];
for (int i = array.length - 1; i>=0;--i){
sortedArray[countArray[array[i]-min]-1] = array[i];
countArray[array[i]-min]--;
}
return sortedArray;
}
补充说明
1.当数列最大和最小值差距过大时,空间开销太大,不适合用计数排序
2.当数列元素不是整数时,也不适合用计数排序
桶排序
简介
桶排序的出现就是为了解决计数排序不适用的2种情景。
桶排序同样也是一种线性时间的排序算法。类似于计数排序所创建的统计数组,桶排序需要创建若干个桶来协助排序
复杂度
时间复杂度:O(n)
空间复杂度:O(n)
代码实现
//桶排序
public static double[] bucketSort(double[] array){
double max = array[0];
double min = array[0];
//得到最大值与最小值
for (int i = 1;i<array.length; ++i){
if (array[i]>max)
max = array[i];
if(array[i]<min)
min = array[i];
}
//创建桶
int bucketNum = array.length;
ArrayList<LinkedList<Double>> bucketList = new ArrayList<LinkedList<Double>>(bucketNum);
for (int i = 0; i<array.length;++i){
bucketList.add(new LinkedList<Double>());
}
//遍历原始数组,将每个元素放入桶
for (int i = 0; i<array.length; ++i){
int num = (int)((array[i] - min) * (bucketNum - 1) / (max - min));
bucketList.get(num).add(array[i]);
}
//对每个桶内部进行排序
for (int i = 0; i<bucketList.size();++i){
//调用Collections内部排序函数
Collections.sort(bucketList.get(i));
}
//建立排好序的数组并返回
double[] sortedArrray = new double[array.length];
int index = 0;
for (LinkedList<Double> list : bucketList){
for(double element:list){
sortedArrray[index++] = element;
}
}
return sortedArrray;
}
完整代码
package com.dataStructure;
import java.lang.reflect.Array;
import java.util.*;
/**
* author zbl
*
* datetime 2019.11.9
*
*/
public class Main {
//冒泡排序
public static void sort(int[] array) {
//记录最后一次交换的位置
int lastExchangeIndex = 0;
//无序数列的边界,每次只需比较到这里为止
int sortBorder = array.length - 1;
for (int i = 0; i < array.length - 1; ++i) {
boolean flag = true;
for (int j = 0; j < sortBorder; ++j) {
if (array[j] > array[j + 1]) {
array[j] = array[j] ^ array[j + 1];
array[j + 1] = array[j] ^ array[j + 1];
array[j] = array[j] ^ array[j + 1];
//更新最后一次交换的位置
lastExchangeIndex = j;
flag = false;
}
}
//更新边界
sortBorder = lastExchangeIndex;
if (flag)
break;
}
}
//鸡尾酒排序
public static void jwSort(int[] array) {
for (int i = 0; i < array.length / 2; ++i) {
boolean flag = true;
//奇数轮,从左往右
for (int j = i; j < array.length - 1; ++j) {
if (array[j] > array[j + 1]) {
array[j] = array[j] ^ array[j + 1];
array[j + 1] = array[j] ^ array[j + 1];
array[j] = array[j] ^ array[j + 1];
flag = false;
}
}
if (flag)
break;
//重置标记
flag = true;
//偶数轮,从右往左
for (int j = array.length - 1; j > i; --j) {
if (array[j] < array[j - 1]) {
array[j] = array[j] ^ array[j - 1];
array[j - 1] = array[j] ^ array[j - 1];
array[j] = array[j] ^ array[j - 1];
flag = false;
}
}
if (flag)
break;
}
}
//快排
public static void quickSort(int[] array, int startIndex, int lastIndex) {
//递归结束条件
if (startIndex >= lastIndex)
return;
//得到基准元素
int pivotIndex = singlePartition(array, startIndex, lastIndex);
//根据基准元素,分成2部分进行排序
quickSort(array, startIndex, pivotIndex - 1);
quickSort(array, pivotIndex + 1, lastIndex);
}
//快排辅助函数 双边循环
public static int partition(int[] array, int startIndex, int lastIndex) {
//取第一个元素为基准元素
int pivot = array[startIndex];
int left = startIndex;
int right = lastIndex;
while (left != right) {
//控制right指针比较并左移
while (left < right && array[right] > pivot) {
--right;
}
//控制left指针比较并右移
while (left < right && array[left] <= pivot) {
++left;
}
//交换left和right指针所指向元素
if (left < right) {
array[left] = array[left] ^ array[right];
array[right] = array[left] ^ array[right];
array[left] = array[left] ^ array[right];
}
}
//pivot和指针重合点交换
array[startIndex] = array[left];
array[left] = pivot;
return left;
}
//快排非递归实现
public static void NoQuickSort(int[] array, int startIndex, int lastIndex){
//用栈实现非递归快排
Stack<Map<String,Integer>> quickSortStack = new Stack<Map<String, Integer>>();
//整个数列的起止下标入栈
Map rootParam = new HashMap();
rootParam.put("startIndex",startIndex);
rootParam.put("endIndex",lastIndex);
quickSortStack.push(rootParam);
//栈非空
while(!quickSortStack.isEmpty()){
//栈顶元素出栈
Map<String,Integer> param = quickSortStack.pop();
//得到基准元素位置
int pivotIndex = singlePartition(array,param.get("startIndex"),param.get("endIndex"));
//根据基准元素将数列分成2部分
if(param.get("startIndex") < pivotIndex - 1){
Map<String,Integer> leftParam = new HashMap<String,Integer>();
leftParam.put("startIndex",param.get("startIndex"));
leftParam.put("endIndex",pivotIndex - 1);
quickSortStack.push(leftParam);
}
if(pivotIndex + 1 < param.get("endIndex")){
Map<String,Integer> rightParam = new HashMap<String,Integer>();
rightParam.put("startIndex",pivotIndex + 1);
rightParam.put("endIndex",param.get("endIndex"));
quickSortStack.push(rightParam);
}
}
}
//快排辅助函数 单边循环
public static int singlePartition(int[] array, int startIndex, int lastIndex){
//取第一个元素为基准元素
int pivot = array[startIndex];
int mark = startIndex;
for (int i = startIndex + 1;i<=lastIndex;++i){
if (array[i] < pivot){
mark++;
int p = array[mark];
array[mark] = array[i];
array[i] = p;
//请勿用下列方式进行两值得交换,此处会发生i==mark ,使得异或值变为零
// array[mark] = array[i]^array[mark];
// array[i] = array[i]^array[mark];
// array[mark] = array[i]^array[mark];
}
}
array[startIndex] = array[mark];
array[mark] = pivot;
return mark;
}
//堆排序 升序
public static void heapSort(int[] array){
//把无序数组构建成大顶堆
for (int i = (array.length - 2) / 2;i>=0;--i){
downAdjust(array,i,array.length);
}
//输出构建好的大顶堆
// System.out.println(Arrays.toString(array));
//循环删除堆顶元素,调整堆产生新的堆顶
for (int i = array.length - 1;i>0;--i){
//最后一个元素和第一个元素交换
array[i] = array[0]^array[i];
array[0] = array[0]^array[i];
array[i] = array[0]^array[i];
//执行下沉操作
downAdjust(array,0,i);
}
}
//堆排辅助函数,下沉操作
public static void downAdjust(int[] array, int parentIndex, int length){
//temp保存根结点信息
int temp = array[parentIndex];
int childIndex = parentIndex * 2 + 1;
//大顶堆
while (childIndex < length){
//如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子
if (childIndex + 1 < length && array[childIndex + 1] > array[childIndex]){
childIndex++;
}
//如果父节点大于任何一个孩子的值,则直接跳出
if (temp >= array[childIndex])
break;
//无需赋值,单向交换
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = parentIndex * 2 + 1;
}
//最后赋值
array[parentIndex] = temp;
}
//计数排序
public static int[] countSort(int[] array){
int max = array[0];
int min = array[0];
//得出数组最大值与最小值
for (int i = 1; i<array.length;++i){
if (array[i]>max){
max = array[i];
}
if (array[i]<min) {
min = array[i];
}
}
//创建数组
int[] countArray = new int[max - min + 1];
//计数
for (int i = 0; i<array.length;++i ){
countArray[array[i]-min]++;
}
//统计数组做变形,后面的元素等于前面的元素相加
for (int i = 1; i<countArray.length;++i){
countArray[i] += countArray[i - 1];
}
//倒序遍历原始数组,从统计数组中找到正确的位置,输出到结果数组
int[] sortedArray = new int[array.length];
for (int i = array.length - 1; i>=0;--i){
sortedArray[countArray[array[i]-min]-1] = array[i];
countArray[array[i]-min]--;
}
return sortedArray;
}
//桶排序
public static double[] bucketSort(double[] array){
double max = array[0];
double min = array[0];
//得到最大值与最小值
for (int i = 1;i<array.length; ++i){
if (array[i]>max)
max = array[i];
if(array[i]<min)
min = array[i];
}
//创建桶
int bucketNum = array.length;
ArrayList<LinkedList<Double>> bucketList = new ArrayList<LinkedList<Double>>(bucketNum);
for (int i = 0; i<array.length;++i){
bucketList.add(new LinkedList<Double>());
}
//遍历原始数组,将每个元素放入桶
for (int i = 0; i<array.length; ++i){
int num = (int)((array[i] - min) * (bucketNum - 1) / (max - min));
bucketList.get(num).add(array[i]);
}
//对每个桶内部进行排序
for (int i = 0; i<bucketList.size();++i){
//调用Collections内部排序函数
Collections.sort(bucketList.get(i));
}
//建立排好序的数组并返回
double[] sortedArrray = new double[array.length];
int index = 0;
for (LinkedList<Double> list : bucketList){
for(double element:list){
sortedArrray[index++] = element;
}
}
return sortedArrray;
}
public static void main(String[] args) {
// write your code here
double[] array = new double[]{2.22,1.2,3.2,4.9,5.3,7.8};
array = bucketSort(array);
System.out.println("桶排序:\n" + Arrays.toString(array));
int[] arr1 = new int[] {2,3,1,9,5,3,4};
arr1 = countSort(arr1);
System.out.println("计数排序:\n" + Arrays.toString(arr1));
arr1 = new int[] {2,3,1,9,5,3,4};
heapSort(arr1);
System.out.println("堆排序:\n" + Arrays.toString(arr1));
arr1 = new int[] {2,3,1,9,5,3,4};
NoQuickSort(arr1,0,arr1.length - 1);
System.out.println("非递归快排:\n" + Arrays.toString(arr1));
arr1 = new int[] {2,3,1,9,5,3,4};
quickSort(arr1,0,arr1.length - 1);
System.out.println("快排:\n" + Arrays.toString(arr1));
arr1 = new int[] {2,3,1,9,5,3,4};
jwSort(arr1);
System.out.println("鸡尾酒排序:\n" + Arrays.toString(arr1));
arr1 = new int[] {2,3,1,9,5,3,4};
sort(arr1);
System.out.println("冒泡排序:\n" + Arrays.toString(arr1));
}
}
各排序算法复杂度总结
参考文献
- 王红梅,胡明,王涛编著. 数据结构(C++版)(第二版)[M]. 清华大学出版社.
- 魏梦舒(@程序员小灰)著. 漫画算法—小灰的算法之旅 [M]. 电子工业出版社.