目录
一.排序概念
1.1 排序的定义:
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
我们将逐步讲解以上常见的排序!
二. 插入排序
定义:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
更常见的例子就是在玩扑克牌的时候,将牌排序好
2.1 直接插入排序
思想:
i从1下标位置遍历,每次将i下标位置的数据存储在tmp中,j为i - 1,if(tmp < array[j])将j下标的值给j+1下表,将数据前移,else 直接break;最后,在将tmp下表给array[j+1]!
public void insertSort(int[] array){
for (int i = 1;i < array.length;i++){
int j = i - 1;
int tmp = array[i];
for(;j >= 0;j--){
if(array[j] > tmp){
array[j + 1] = array[j];
}else{
break;
}
}
array[j + 1] = tmp;
}
}
根据思想:
时间复杂度:O(N^2)
空间复杂度:O(1),它是一种稳定的排序算法
2.2 希尔排序(对直接插入排序的优化)
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序
是不是有些难以理解呢?
我们举个例子来理解其内涵:
先进行第一次gap = 5的预排序!
、
让其进行直接插入排序
接着就是gap = 2
最后就是gap = 1的情况了
Ps:这里的排序是对直接插入排序的优化
至于gap的取值,是一个非常复杂的数学难题,我们通常使用 其长度/2 的方式来获得gap!
然后其复杂度 我们通常以结论的形式记忆:O(N^1.3)
代码实现:
public void shellSort(int[] array){
int k = array.length;
while(k > 1){
k = k / 2;
shell(array,k);
}
}
public void shell(int[] array,int gap){
for(int i = gap; i < array.length;i += gap){
int j = i - gap;
int tmp = array[i];
for(;j >= 0;j--){
if(tmp < array[j]){
array[j + gap] = array[j];
}else{
break;
}
}
array[j + gap] = tmp;
}
}
三. 选择排序
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
3.1 直接选择排序
简而言之呢,就是套俩循环,从i下标的位置出发,定义一个minIndex 初始值为 i 然后j = i + 1 在进入循环,如果发现arr [j]< arr [minIndex] ,将二者进行交换,然后minIndex 改为j
public void selectSort(int[] array){ for(int i = 0 ;i < array.length ;i++){ int j = i + 1; int minIndex = i; for(;j < array.length;j++){ if(array[minIndex] > array[j]){ //交换 int tmp = array[minIndex]; array[minIndex] = array[j]; array[j] = tmp; minIndex = j; } } } }
非常好理解,但是效率过于低下
3.2 堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆
本质是数组,思维是二叉树
public void heapSort(int[] array){
creatBigHeap(array);
int end = array.length - 1;
while(end > 0){
swap(array,0,end);
shiftDown(array,0,array.length);
end--;
}
}
public void creatBigHeap(int[] array){
for(int i = (array.length - 1 - 1) / 2 ; i >= 0; i--){
shiftDown(array,i,array.length);
}
}
public void shiftDown(int[] array,int parent,int len){
int child = (parent * 2) + 1;
while(child < len){
if(child + 1 < len && array[child] > array[child + 1]){
child++;
}
if(array[child] > array[parent]){
swap(array,child,parent);
parent = child;
child = (parent * 2) + 1;
}else{
break;
}
}
}
public void swap(int[] array,int i , int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
时间复杂度:O(N*logN)
空间复杂度:O(1)
稳定性:不稳定
四. 交换排序
基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。
交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动
4.1 冒泡排序
冒泡排序我们已经非常熟悉了,这里就不多赘述了!
4.2 快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
1.Hoare版
public void quickSort(int[] array,int start,int end){
if(start >= end){
return;
}
int pivot = partition(array,start,end);
quickSort(array,start,pivot - 1);
quickSort(array,pivot + 1,end);
}
public int partition(int[] array,int left,int right){
int tmp = array[left];
int i = left;
while(left < right){
while(left < right && array[right] > tmp){
right--;
//遇到比tmp小的就停下来
}
while(left < right && array[left] < tmp){
left++;
}
swap(array,left,right);
}
swap(array,i,left);
return left;
}
这里是一段错误代码
这里应该是
我们有几个问题:
1.为什么要从右边开始找?
只有从右边开始找才可以造成左大右小的局面
2.为什么要 array[left] <= tmp 或者是 >= 呢?
比如说开头和尾部都是pivot,既不left++,也不right-- 俩个自己玩起来了,变成死循环。
当我们给定的数据是有序的时候,这个快排的时间复杂度:O(N^2)
空间复杂度:O(N)
2.挖坑法
与hoare不同的在于,在left++,right--的时候直接赋值,不再需要交换
public int partitionHoare(int[] array,int left,int right){
int tmp = array[left];
int i = left;
while(left < right){
while(left < right && array[right] >= tmp){
right--;
//遇到比tmp小的就停下来
}
while(left < right && array[left] <= tmp){
left++;
}
swap(array,left,right);
}
swap(array,i,left);
return left;
}
3.前后指针
其主要思想是prev跟着cur向后遍历,将大的数据与cur遍历到小的数据进行交换。具体代码如下:
public int partiton(int[] array,int left,int right){
int cur = left + 1;
int prev = left;
while(cur <= right){
if(array[cur] < array[left] && array[++prev] != array[cur]){
swap(array,cur,prev);
}
cur++;
}
swap(array,prev,left);
return prev;
}
①优化:
当数据有序时,快排时间复杂度达到最大O(N^2),空间复杂度。我们知道,如果将有序的数据利用分治思想,分而治之就可以降低时间复杂度。那么我们应该怎么做呢?
使用三数取中法!
这样做的好处就是right不必 -- 到left处,极大的节省了空间!
具体做法就是在找基准之前就找到mid的值,然后将其与left下标交换
public void quickSort(int[] array,int start,int end){
if(start >= end){
return;
}
//找基准之前 解决分布不均匀问题,(解决有序问题)
// int index = findMidValOfIndex(array,start,end);
// swap(array,start,index);
int pivot = partition2(array,start,end);
quickSort(array,start,pivot - 1);
quickSort(array,pivot + 1,end);
}
private int findMidValOfIndex(int[] array,int start,int end){
int mid = (start + end) / 2;
if(array[start] < array[end]){
if(array[mid] < array[start]){
return start;
}else if(array[mid] > array[end]){
return end;
}else{
return mid;
}
}else{
if(array[mid] < array[end]){
return end;
}else if(array[mid] > array[start]){
return start;
}else{
return mid;
}
}
②优化:
递归到很小的时候,可以考虑插入排序
在小范围内进行插入排序
public void quickSort(int[] array,int start,int end){
if(start >= end){
return;
}
if(end-start+1 <= 15) {
//对 start 和 end区间 范围内 使用插入排序
insertSort(array,start,end);
return;
}
int pivot = partition2(array,start,end);
quickSort(array,start,pivot - 1);
quickSort(array,pivot + 1,end);
}
private void insertSort(int[] array,int left,int right){
for(int i = left + 1;i <= right;i++){
int tmp = array[i];
int j = i - 1;
for(;j >= left;j--){
if(array[j] > tmp){
array[j + 1] = array[j];
}else{
break;
}
}
array[j + 1] = tmp;
}
}
写到这里,我们升级一下难度:利用非递归的形式实现快排!
利用栈来实现
思路:先将(看作是)左子树的start,end下标入栈,然后是右子树。在入栈的时候要注意是否左右只有一个元素!
在while里实现了类似递归的操作
public void quickSortNor(int[] array){
int start = 0;
int end = array.length - 1;
Stack<Integer> stack = new Stack<>();
int pivot = partition2(array,start,end);
//左子树push
if(pivot > start + 1){
stack.push(start);
stack.push(pivot - 1);
}
//右子树push
if(pivot < end - 1){
stack.push(pivot + 1);
stack.push(end);
}
//将栈不会空为执行条件
while(!stack.isEmpty()){
//pop的时候先用end接受,再是strat
end = stack.pop();
start = stack.pop();
pivot = partition2(array,start,end);
//重复操作
//左子树push
if(pivot > start + 1){
stack.push(start);
stack.push(pivot - 1);
}
//右子树
if(pivot < end - 1){
stack.push(pivot + 1);
stack.push(end);
}
}
}
1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(logN)
4. 稳定性:不稳定
五. 归并排序
5.1 归并排序的基本思想
归并排序可以用一个图来表示:
public void mergeSort(int[] array){;
mergeSortChild(array,0,array.length - 1);
}
public void mergeSortChild(int[] array,int left,int right){
int mid = (left + right) / 2;
if(left >= right){
return;
}
mergeSortChild(array,left,mid);
mergeSortChild(array,mid+1,right);
//合并
merge(array,left,mid,right);
}
private void merge(int[] array,int strat,int mid ,int end){
int s1 = strat;
int e1 = mid;
int s2 = mid + 1;
int e2 = end;
int[] tmpArr = new int[end - strat + 1];
int k = 0;
while(s1 <= e1 && s2 <= e2){
if(array[s1] > array[s2]){
tmpArr[k++] = array[s2++];
}else{
tmpArr[k++] = array[s1++];
}
}
while(s1 <= e1){
tmpArr[k++] = array[s1++];
}
while(s2 <= e2){
tmpArr[k++] = array[s2++];
}
for(int i = 0; i < k;i++){
array[i+strat] = tmpArr[i];
}
}
那么归并排序的非递归怎么实现的呢?
代码实现:
private void mergeSortChildNor(int[] array,int left,int right){
int gap = 1;
while(gap < array.length){
for(int i = 0;i < array.length;i += gap*2){
int start = i;
int mid = start + gap - 1;
int end = mid + gap;
if(mid >= array.length){
mid = array.length - 1;
}
if(end >= array.length){
end = array.length - 1;
}
merge(array,start,mid,end);
}
gap = gap * 2;
}
}
归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
时间复杂度:O(N*logN)
空间复杂度:O(N)
稳定性:稳定
我们已经学了不少的排序,总结一下
六. 海量数据的排序问题
这类问题主要采取的分治思想
比如说 有1TB的数据需要进行排序,而我们却只有32GB的内存
1.那么我们可以将1TB数据分为40份:每份的数据是 25GB
2.分别对每份数据进行任何方式的排序,使每个25GB的数据变成有序
3.然后可以 将40份数据(25/40=0.625)的0.625都放入内存中进行归并,就实现排序了
计数排序
public void countSort(int[] array){
int min = array[0];
int max = array[0];
//1.找到大小极值
for(int i = 1;i < array.length;i++){
if(array[i] > max){
max = array[i];
}
if(array[i] < min){
min = array[i];
}
}
//2.计算长度
int len = (max - min) + 1;
int[] tmpArr = new int[len];
//3.遍历array,并计数
for(int i = 0;i < array.length;i++){
tmpArr[array[i] - min] ++;
}
//4.覆盖
int index = 0;
for(int i = 0;i < tmpArr.length;i++){
while(tmpArr[i] > 0){
array[index] = i + min;
index++;
tmpArr[i]--;
}
}
}