目录
堆排序(选择排序)
堆排序是基于选择排序的一种排序算法,可以算是选择排序的优化。
核心思想:选择排序是每次选中最大元素的下标放到无序数组的最后边,堆排序基于选择排序对选择最大元素的过程进行了优化,堆排序的选择最大元素的过程是 依靠建大堆实现(升序排列建大堆),堆顶元素下标(也就是0号下标)就是每次的最大元素下标,把它和无序区间的最后一个元素交换。
实现代码:
public static void heapSort2(long[] array){
creatHeap2(array,array.length);//建一个大堆
//堆选择
for (int i = 0; i < array.length-1 ; i++) {
//有序区间:[size-i,size)
//无序区间:[0,size-i)
//建堆完,现在已经是大堆了
swap2(array,0,array.length-i-1);
shiftDown2(array,array.length-i-1,0);
}
}
public static void creatHeap2(long[] array,int size){
int lastSize=size-1;
int lastParentSie=(lastSize-1)/2;
for(int i=lastParentSie;i>=0;i--){
shiftDown2(array,size,i);
}
}
public static void shiftDown2(long[] array,int size,int index){
while(true) {
int leftIdx = index * 2 + 1;
if (leftIdx >= size) {
return;
}
int max = leftIdx;
if (leftIdx + 1 < size && array[max + 1] > array[max]) {
max = leftIdx + 1;
}
if (array[index] > array[max]) {
return;
}
swap2(array, index, max);
index=max;
}
}
public static void swap2(long[] array,int i, int j){
long t=array[i];
array[i]=array[j];
array[j]=t;
}
时间复杂度
时间复杂度有俩个部分构成,堆得向下调整阶段时间复杂度为O(log(n))
堆选择(进行多少次取最大元素过程)时间复杂度为O(n)
因此最终时间复杂度为O(n*log(n))
希尔排序(插入排序)
希尔排序是插入排序的优化版,也称为缩小增量排序。
核心思想:把待排序的数组元素,按照下标进行一定的增量分组,分为多个子序列,对各个子序列进行插入排序,然后依次缩减增量,在进行排序,直到增量为1时,进行最后一次插入排序,排序完毕。
代码实现:
private static void inserSortWithGroup(long[] array,int group){
//i的含义要插入的元素,插入排序要插入的元素从1开始,希尔排序要插入的元素从group开始
//插入排序相当于增量为1,而希尔排序,每次增量为group
for(int i=group;i<array.length;i++){
long key=array[i];
int j;
for(j=i-group;j>-0 && key<array[j];j=j-group){
array[j+group]=array[j];
}
array[j+group]=key;
}
}
时间复杂度
希尔排序的时间复杂度比较特殊,O(n^1.3)
快速排序
基本思想:通过不断的寻找基准值,让序列中元素和基准值比较大小,把基准值作为标准,小于基准值的处于基准值左侧,大于基准值的处于基准值右侧。直到最后排序完毕。
快速排序用到了partition实现。
partition:把每个元素都和基准值进行比较,并做一定的移动保证,让比基准值小的元素都在基准值的左边,比基准值大的元素都在基准值的右边,这个过程叫做partition.
快速排序主要应用partition实现,partition的方法有好几种,因此快速排序的实现也有好几种方式。
时间复杂度
O(n*log(n))
下边介绍几种partition方式,和相应的partition方式实现的快速排序。
1.Horve法
指针分别指在两端,最左边放小于pivot(基准值)的元素,最右边放大于pivot的元素,通过不断地比较,两个指针不断的向中间靠拢,中间未比较区域越来越小,直至最后两个指针相遇,此时排序完毕。
基准值在左边,先动右边;基准值在右边,先动左边。
代码实现:
private static int partition1(long[] array,int fromIdx,int toIdx){
int leIdx=fromIdx;
int getIdx=toIdx;
long pivot=array[toIdx];//选最后一个为基准值
while(getIdx>leIdx){
//基准值在右边,先动左边的指针
while(getIdx>leIdx && array[leIdx]<=pivot){
leIdx++;
}
//出来上一个循环则代表左边碰到了比它大的元素或者左右两个指针已经相遇
while(getIdx>leIdx && array[getIdx]>=pivot){
getIdx--;
}
//这里说明,右边碰到了比它小的元素。此时左右两指针指向的两个元素交换位置
swap(array,leIdx,getIdx);
return leIdx;
}
swap(array,leIdx,toIdx);
return leIdx;
}
private static void swap(long[] array,int i,int j){
long t=array[i];
array[i]=array[j];
array[j]=t;
}
private static void quickSortRange(long[] array,int fromIdx,int toIdx){
//求出本次处理的无序区间的元素个数
int size=toIdx-fromIdx+1;
if(size<=1){
return;
}
//pivotIdx是partition后,pivot所在的下标
int pivotIdx=partition1(array,fromIdx,toIdx);
//整个区间[fromIdx,toIdx]被pivot分为两部分
//左边:[fromIdx,pivotIdx-1]
//右边:[pivot+1,toIdx]
quickSortRange(array,fromIdx,pivotIdx-1);
quickSortRange(array,pivotIdx+1,toIdx);
}
关于最后一次交换的解释:
在快速排序算法中,每个分区的最终目标是将小于等于基准值的元素放在左侧,将大于基准值的元素放在右侧,并将基准值放在正确的位置上。因此,在partition1
方法中,左右指针的交换仅仅是将当前循环中找到的小于基准值的元素与大于基准值的元素进行交换,而不一定将其放置在最终正确的位置上。
在循环结束时,我们知道左侧指针leIdx
左侧的所有元素都小于等于基准值,右侧指针getIdx
右侧的所有元素都大于基准值。因此,将基准值(右侧指针)与左侧指针所指向的元素进行交换可以将基准值放置在正确的位置上,同时保证左侧的元素都小于等于基准值,右侧的元素都大于基准值。
但是,最后一个交换可能是不必要的。当左右指针相遇时,它们所指向的元素都已经被交换到了正确的位置。如果这种情况发生了,即左右指针没有经过交换就相遇了,那么最后一个交换就会交换左指针和右指针的元素,从而将它们错误地恢复到它们最初的状态。因此,在进行最后一个交换之前,需要确保左右指针实际上指向了不同的元素。
2,前后指针法
这个主要是指针初始位置指向不同,初始指针都指向fromIdx,
区间划分为:[小于基准值][大于基准值][未比较的区间]
代码实现:
private static int partition2(long[] array,int fromIdx,int toIdx){
int left=fromIdx;
int right=fromIdx;
//小于基准值:[fromIdx,left)
//大于基准值:[left,right)
//未比较:[right,toIdx)
long pivot=array[toIdx];
while(right<toIdx){
if(array[right]>pivot){
right++;
}else{
swap(array,left,right);
left++;
right++;
}
}
swap(array,left,toIdx);
return left;//返回基准值
}
3.挖坑法
是对Horve的优化
代码实现:
private static int partition2(long[] array, int fromIdx, int toIdx) {
int leIdx = fromIdx;
int geIdx = toIdx;
long pivot = array[toIdx];
// 小于等于基准值的区间: [fromIdx, leIdx) 区间 1
// 大于等于基准值的区间:[geIdx, toIdx] 区间 2
// 没有和基准值比较的区间: [leIdx, geIdx) 区间 3
// 停止条件: 区间 3 的元素个数是 0 个
while (geIdx > leIdx) {
// 基准值在右侧,先动左边
while (geIdx > leIdx && array[leIdx] <= pivot) {
leIdx++; // leIdx 在变化,变化过程中,geIdx > leIdx 条件可能会被破坏
}
// 左侧遇到一个大于基准值的元素了
array[geIdx] = array[leIdx];
while (geIdx > leIdx && array[geIdx] >= pivot) {
geIdx--;
}
// 右侧遇到一个小于基准值的元素了
array[leIdx] = array[geIdx];
}
array[leIdx] = pivot;
return leIdx;
}
4.把等于 pivot 的同时处理到一块
private static int[] partition4(long[] array, int fromIdx, int toIdx) {
int ltIdx = fromIdx;
int eqIdx = fromIdx;
int gtIdx = toIdx;
long pivot = array[toIdx];
// 小于: [fromIdx, ltIdx)
// 等于: [ltIdx, eqIdx)
// 未比较: [eqIdx, gtIdx]
// 大于: (gtIdx, toIdx]
while (eqIdx <= gtIdx) {
if (array[eqIdx] == pivot) {
eqIdx++;
} else if (array[eqIdx] < pivot) {
swap(array, ltIdx, eqIdx);
ltIdx++;
eqIdx++;
} else {
swap(array, eqIdx, gtIdx);
gtIdx--;
}
}
return new int[] { ltIdx - 1, gtIdx + 1 };
}
归并排序
基本思想:把数组平均分为两部分,左半部分和右半部分已经有序了,直接合并两个有序数组变成一个有序数组 若左右两部分无序,则继续划分,直到左右两个部分有序,合并数组。主要就是把大问题划分为子问题,解决一个个子问题,子问题在最小规模(区间元素个数是0或1时候)是可以解决的。
代码实现:
private static void merge(long[] array,int fromIdx,int midIdx,int toIdx) {
//顺序表合并需要额外空间
int size = toIdx - fromIdx;
long[] temp = new long[size];//合并链表时临时数组
//一共需要几个下标变量
//左边:[fromIdx,midIdx]
//右边;[midIdx,toIdx]
//临时:[0,size]
int i = fromIdx;
int j = midIdx;
int k = 0;
//当左右两个有序区间都还有元素时候,分别找出最小的,把最小的尾插到临时区间
while (i < midIdx && j < toIdx) {
if (array[i] <= array[j]) {
temp[k] = array[i];
i++;
k++;
} else {
temp[k] = array[j];
j++;
k++;
}
}
//把还有元素的区间的剩余元素全部尾插到临时区间
while (i < midIdx) {
temp[k] = array[i];
i++;
k++;
}
while (j < toIdx) {
temp[k] = array[j];
j++;
k++;
}
//把额外区间的元素搬回原来的位置
//额外区间:[0,size]
//原来区间:[fromIdx,toIdx]
for (int n = 0; n < size; n++) {
array[n + fromIdx] = temp[n];
}
}
private static void mergeSortRange(long[] array,int fromIdx,int toIdx){
int size=toIdx-fromIdx;
if(size <=1){
return;
}
int midIdx=(fromIdx+toIdx)/2;
//先排左边,再排右边
mergeSortRange(array,fromIdx,midIdx);
mergeSortRange(array,midIdx,toIdx);
merge(array,fromIdx,midIdx,toIdx);
}
public static void mergeSort(long[] array){
mergeSortRange(array,0,array.length);
}
时间复杂度
O(n*log(n))
空间复杂度
O(n)因为需要额外的空间存储
以上是这篇的主要内容,望指正!