概述
学习过数据结构这本书的同学都知道,数据结构这本书前面一大部分都在讲述基础的数据结构和基础定义,而在最后面的几章才会讲解排序相关的算法,而对于排序,是比较重要的。无论是在笔试还是在面试的时候都可能会遇到,特别是在面对数组问题的时候。所以今天借此机会,来总结下常见的几种算法:冒泡、选择、插入、归并、快排、堆排(打算放到树的时候讲解),而以下的内容将从三个方面对这些算法讲解:基本思想、代码实现(可能还有改进版本)、是否稳定和时间复杂度以及是否和数据状况有关。
冒泡
一、基本思想
一个指向数组起始元素的指针 i、指向数组末尾元素的指针 j,通过 j 在不大于 i 的范围内,循环的和自己前面的元素值比较,交换比自己大的元素,之后 j 往前走,每一次循环后 i 减一 ,直到i = n-1结束这个过程
二、代码实现
//冒泡排序
void bubbleSort(int[] arr){
for(int i = 0; i < arr.length -1; i++){//i指针从0~n-1
for(int j = arr.length - 1; j > i ; j++){j指针从n-1~i
if(arr[j] < arr[j+1]){//始终和自己前面的元素比较大就交换
swap(arr,j,j+1);
}
}
}
}
void swap(int[] arr,int i,int j){
int tem = arrr[i];
arr[i] = arr[j];
arr[j] = tem;
}
//改进版本,如果在有序的数据状况下,让冒泡不要做无畏的遍历了
void bubbleSortUp(int[] arr){
boolean flag = true;
for(int i = 0; i < arr.length - 1 && flag ; i++){
flag = false;
for(int j = arr.length - 1; j > i; j--){
if(arr[j] < arr[j+1]){
swap(arr,j,j+1);
flag = true;
}
}
}
}
三、时间复杂度、稳定性、数据状况分析
时间复杂度:T(N) = O(N) = n-1+n-2+n-3+…1 = O(N^),根据bigO的原则,忽视常数项、忽视次高项、忽视最高项的常数
稳定性:稳定性是指相同数值的元素在未排序之前的相对顺序在排好了序的数组中的相对次序不变,能做到这一点便是稳定,而冒泡可以做到稳定,即在比较的时候遇到相同的原素值的时候不交换即可
数据状况:和数据状况无关,即数据本身是有序的时候还是时间复杂度还是O(N^2),因为它的流程已经固定了,有序只是不交换而已,但还是会循环遍历
选择排序
一、基本思想
用三个指针,i 指向数组的起始,j指向i之后的数据,min指向数组中每次的最小元素的最小值,每次遍历之后把min和i元素交换
二、代码实现
void selectSort(int[] arr){
int min = 0;
for(int i = 0; i < arr.length-1; i++){
min = i;
for(int j = i + 1; j < arr.length; j++){
if(arr[j] < arr[min]){
min = j;
}
}
swap(arr,i,min);
}
}
三、时间复杂度、稳定性、数据状况
时间复杂度:T(N) = O(N) = n-1 + n-2 + n-3 + …1 = O(N^2)
稳定性:不能做到稳定性,如果数组一开始就有很多相同的元素在后面又有比这些相同元素小的元素,那么交换的时候就打乱了稳定性
数据状况:与数据状况无关,思路和冒泡一样
插入排序
一、基本思想
起初一个元素已经有序(本身即有序),之后来的元素和之前有序的数组的最后一个元素比较,如果比他大就直接插在它后面,如果比他小那么就在之前有序的数组中寻找合适的位置插入即可
二、代码实现
void insertSort(int[] arr){
for(int i = 1; i < arr.length ; i++){
for(int j = i-1; j >= 0 && arr[j] > arr[j+1]; j--){
swap(arr,j,j+1);
}
}
}
三、时间复杂度、稳定性、数据状况
时间复杂度:
时间复杂度和其数据状况有关,数据有序的时候即arr[j] < arr[j+1]就不用寻找插入位置了,即时间复杂度O(N)
如果数据无序,那么就要每次都要去寻找插入节点位置,O(N) = 1+2+3+…n-1 = O(N^2)
稳定性:可以做到稳定性,遇到相同的就不交换
数据状况:和时间复杂度有关,见时间复杂度分析
快速排序
一、基本思想
将一个数组中的数据元素分成小于给定指定值在数组的前部分,等于的在中间部分,大于的在后面部分。具体实现思路是用三个指针,一个指针指向小于区域(即小于给定指值的部分的末尾元素),大于区域,即指向大于给定值的最开始的那个数,cur指针指向当前元素,cur每次遍历都和给定值比较,大于就和大于区域的前一个数交换,cur不变,因为交换过来的数还要考察是否大于等于小于;如果小于就和小于区域的后一个数交换,且cur往前走,直到cur=大于区域指针,如果等于那么cur就直接往前走即可
二、代码实现
//默认按数组最后一个元素划分
void quickSort(int[] arr,int left,int rigth){
if(left >= rigth)//left >= right说明一次partion完成
return;
in partionIndex = partion(arr);//获取切入点
quickSort(left,partionIndex-1);//递归左子数组
quickSort(partionIndex + 1; rigth);//递归右子数组
}
int partion(int[] arr){
int cur = 0;//当前指针
int min = -1;//小于区域指针
int max = arr.length-1;//大于区域指针
while(cur != max){
if(arr[cur] > arr[max]){//当前值大于给定值
swap(arr,cur,max);
}else if(arr[cur] < arr[max]){//当前值小于给定值
swap(arr,cur,++min);
cur++;
}else{
cur++;//当前值等于给定值
}
}
//swap the last elemnt
swap(arr,min,max);
return min;
}
三、时间复杂度、稳定性、数据状况
时间复杂度:如果按给定值划分的左右子数组规模相差很大,那么就会造成O(N^2)的时间复杂度,因为每一次都是只减少一个数据规模,那么就会经历n-1+n-2+n-3+…1的过程;但若每次按划分的左右子数组的规模都是相差不大,根据master公式得出T(N) = O(nlogn)
稳定性:不能做到稳定性,参考选择排序
数据状况:很明显和数据状况有关系,如果数据状况按给定值能划分成左右子数组规模相差不大的话时间复杂度很快的,如果相差很大那么就成为了O(N^2)
四、快排改进
一、基于数据规模每次只减少一个来改进,即争取每次多减少数据规模
public static void quickSort(int[] arr, int left, int rigth) {
if (left < right) {
int[] p = partition(arr, left, rigth);
quickSort(arr, left, p[0] - 1);//左子数组的右边界是相等区域的后一个
quickSort(arr, p[1] + 1, rigth);//右子数组的左边界是相等区域的前一个
}
}
public static int[] partition(int[] arr, int l, int r) {
int less = l - 1;//小于指针域
int more = r;
while (l < more) {
if (arr[l] < arr[r]) {
swap(arr, ++less, l++);
} else if (arr[l] > arr[r]) {
swap(arr, --more, l);
} else {
l++;
}
}
swap(arr, more, r);
return new int[] { l, more };//返回相等区域的左右边界
}
二、虽然数据规模一次可以减少更多了,当还是可能造成左右数组规模分割不一致的情况
这时候随机的将数组中的值和数组最后一个元素交换,每次依据不同的值在左右子数组中来划分,减少了左右划分不一致的概率
public static void quickSort(int[] arr, int l, int r) {
if (l < r) {
swap(arr, l + (int) (Math.random() * (r - l + 1)), r);//随机快排,防止左右两部分数据规模不一致
int[] p = partition(arr, l, r);
quickSort(arr, l, p[0] - 1);
quickSort(arr, p[1] + 1, r);
}
}
public static int[] partition(int[] arr, int l, int r) {
int less = l - 1;//小于指针域
int more = r;
while (l < more) {
if (arr[l] < arr[r]) {
swap(arr, ++less, l++);
} else if (arr[l] > arr[r]) {
swap(arr, --more, l);
} else {
l++;
}
}
swap(arr, more, r);
return new int[] { l, more };
}