以下排序都以升序为主
目录
一、插入排序
基本思想
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
特性:元素集合越接近有序,直接插入排序算法的时间效率越高
方法:
1.定义两个下标i和j,j=i-1.
2.记录i的值temp,让temp和它之前的元素作比较。以升序为例,如果前面的值都比他大一直递归。
3.如果遇到比他小的值则交换,temp放在小值下标+1的位置,并让j--,然后继续判断后面的值。
4.循环条件是j>=0.
public static void inSort(int []array){
for(int i=1;i<array.length;i++){
int j=i-1;
//将i下标的值放在temp中
int temp=array[i];
//i与之前的所有数作比较,如果小继续与前面的作比较,如果大则比较停止
while (j>=0){
//temp的值比j值大则退出循环
if(array[j]<=temp){
break;
}
//j的值比temp的值大则一直把j下标的值往后移
array[j+1]=array[j];
//j往前移
j--;
}
//找到合适位置之后把temp的值放回合适的数组位置
array[j+1]=temp;
}
}
二、希尔排序
基本思想
先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
方法:
1.定义一个gap值,用来记录间隔数,间隔数位gap值的所有数进行比较和交换
2.定义i和j,则j=i-gap,把i的值放在temp。比较j和temp的大小,j=j-gap,直到j<0.
最后把temp放回。
public static void shellSort(int[] array){
//定义一个gap值,间隔几个数
int gap=array.length/2;
while (gap>0){
shell(array,gap);
gap/=2;
}
}
private static void shell(int[]array,int gap){
for(int i=gap;i<array.length;i++){
//i与j相差gap
int j=i-gap;
//临时空间储存i下标的值
int temp=array[i];
while(j>=0) {
//j的值小于temp的值不做处理
if (array[j] <= temp) {
break;
}
//把j下标的值放在原来i的位置上
array[j+gap] = array[j];
//j-gap后不小于0,说明前面还有一个间隔gap的值,需要再进一次循环做处理
j-=gap;
}
//循环结束把temp的值放在j+gap的位置
array[j+gap]=temp;
}
}
三、直接选择排序
基本思想
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
方法一(在一次遍历中找出最小值):
1.定义第一个元素下标minIndex为最小值,用i遍历之后的每一个元素,和minIndex作比较,如果比它小,交换两个的值。
2.然后定义第二个数,遍历之后的元素做同样的操作。
/*
直接选择排序 升序排列
*/
public static void selectSort(int[] array){
for (int i=0;i<array.length;i++){
//定义最小值下标
int minIndex=i;
//将i后的所有值与i作比较
for (int j=i+1;j<array.length;j++){
//如果碰到比i小的值更新minIndex
if(array[j]<array[minIndex]) {
minIndex = j;
}
}
//遍历完成后找到的最小值和最开始的最小值做交换
swap(array,minIndex,i);
}
}
private static void swap(int[] array, int minIndex, int i) {
//把找到的最小值和i下标的值作交换
int temp=array[minIndex];
array[minIndex]=array[i];
array[i]=temp;
}
方法二(在一次遍历中同时找出最大值和最小值):
1.定义数组两边为left和right
2.定义第一个元素同时为最大值和最小值,在遍历过程中与第一个元素作比较,值若大更新maxIndex,值小更新minIndex
3.最后交换minIndex和left,maxIndex和right
4.注意当left为maxIndex,right为minIndex的情况时,只交换一次就能达到效果。
public static void selectSort2(int[] array){
//定义left和right在数组两边
int left=0;
int right=array.length-1;
while(left<right){
//初始最大值和最小值都是left
int minIndex=left;
int maxIndex=left;
//遍历开始
for (int i=left+1;i<=right;i++){
//i值小更新minIndex
if(array[i]<array[minIndex]){
minIndex=i;
}
//i值大更新maxIndex
if(array[i]>array[maxIndex]) {
maxIndex = i;
}
}
//左边不为minIndex则交换
if (left!=minIndex){
swap(array,left,minIndex);
}
//最大值最小值刚好互为left right 只交换一次
if(left==maxIndex){
maxIndex=minIndex;
}
//右边不为maxIndex则交换
if(right!=maxIndex){
swap(array,right,maxIndex);
}
left++;
right--;
}
}
四、冒泡排序
基本思想
冒泡排序是交换排序的一种,所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
方法:
1.第一个元素和第二个元素比较,由此相邻的两个元素作比较递归下去,一次执行后最后一个位置的元素就找出来了
2.第二次还是从第一个元素开始比较,最后一个元素位置确定不用比较。
3.重复上述操作
public static void bubbleSort(int[]array){
for (int i=0;i<array.length;i++){
boolean flag=false;
for (int j=0;j<array.length-i-1;j++){
if(array[j]>array[j+1]){
swap(array,j,j+1);
flag=true;
}
}
//如果一直没交换过 说明为有序数组
if(!flag){
break;
}
}
}
五、堆排序
基本思想
利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆
来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
方法(大根堆):
1.建堆:
找到第一个度不为0的父节点
从找到的第一个父节点开始向下调整,满足父节点值最大,遍历整棵树。
2.需要输出为有序数列,所有的节点都需要重新选择位置。由此采取删除元素的方法,把最后一个叶子节点和堆顶元素做交换,采取向下调整的方法找到合适的位置,直到堆中的位置都进行了操作。
public class HeapSort {
//建堆
public static void createHeap(int[] array){
for(int parent=(array.length-2)/2;parent>=0;parent--){
shiftDown(array,parent,array.length);
}
}
//向下调整
private static void shiftDown(int[] array, int parent, int length) {
// 找到左孩子节点
int child = 2 * parent + 1;
//判断是否越界
while (child < length) {
// 判断是否有右孩子
if (child + 1 < length) {
// 如果有右孩子,取左右孩子节点中最大值的下标
if (array[child + 1] > array[child]) {
//child始终在值最大的节点上
child++;
}
}
// 孩子节点中最大的值与父节点的值比较
if (array[child] <= array[parent]) {
//小于等于父节点不做交换
break;
}
// 大于父节点时,交换父子节点
swap(array, parent, child);
// 重置父子节点的下标
parent = child;
child = 2 * parent + 1;
}
}
//交换父节点与子节点
private static void swap(int[] array, int parent, int child) {
int temp = array[parent];
array[parent] = array[child];
array[child] = temp;
}
//排序
public static void sort (int [] array) {
// 1.首先建堆
createHeap(array);
// 2. 确定终止下标
//传进无序堆时,先把堆顶元素和最后一个元素做了交换,所以end应该在倒数第二的位置
int end = array.length - 1;
while (end >= 0) {
// 3. 首尾交换
swap(array, 0, end);
// 4. 向下调整
shiftDown(array, 0, end);
// 5. 修正 end
end--;
}
}
六、快速排序
基本思想
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
方法一(递归):
1.定义数组左右left right 非空校验(left>right)
2.找基准值,使基准值左边都小于基准值,右边都大于基准值
3.从左右子树中再找出基准值重复上述操作
public static void quickSort(int[]array){
quickSortProcess(array,0,array.length-1);
}
private static void quickSortProcess(int[] array, int left, int right) {
if(left>=right){
return ;
}
int pivot = partitionHoare(array,left,right);
partitionHoare(array,left,pivot-1);
partitionHoare(array,pivot+1,right);
}
方法二(非递归):
1.定义一个栈
2.不停地将基准分成的两段的左右边节点放入栈中
3.然后再以相反方向出栈
//快速排序非递归 栈
private static void quickSortProcess2(int[] array, int left, int right) {
Stack<Integer> stack=new Stack<>();
if(left>=right){
return ;
}
int mid=findMid(array,left,right);
swap(array,mid,left);
int pivot = partitionHoare(array,left,right);
if(left+1<pivot){
stack.push(left);
stack.push(pivot-1);
}
if(right-1>pivot){
stack.push(pivot+1);
stack.push(right);
}
if(!stack.isEmpty()){
stack.pop();
stack.pop();
}
}
注:上面的代码没有找基准值代码,下面提供三种找基准值的方法。
找基准解决方法
找基准值方法一:Hoare法
步骤:
1.定义数组左右left right
2.以第一个元素为初始基准值 左右同时向中间移动,左边找到比基准值大的值右边找到比基准值小的值时交换两个值,left++right--。
3.当left right相遇时,则将初始基准值替换为当前节点值,并返回当前节点。
private static int partitionHoare(int[] array, int left, int right) {
int pivotValue=array[left];
int pivotIndex=left;
while (left<right){
while(left<right&&array[right]>pivotValue){
right--;
}
while(left<right&&array[left]<pivotValue){
left++;
}
swap(array,left,right);
}
swap(array,left,pivotIndex);
return left;
}
找基准值方法二:挖坑法
步骤:
1.定义数组左右left right 以第一个元素为初始基准值
2.先判断right和初始基准值大小,如果比基准值大right--,找到比基准值小的值之后交换right值和left的值。
3接着判断left和初始基准值大小,如果比基准值小left++,找到比基准值大的值之后交换left值和right值。
4.当两个相遇时,初始基准值与当前节点交换,并返回该节点。
public static int partitionHole(int[]array, int left,int right){
int pivotValue=array[left];
while(left<right){
while(array[right]>=pivotValue){
right--;
}
array[left]=array[right];
while(array[left]<pivotValue){
left++;
}
array[right]=array[left];
}
array[left]=pivotValue;
return left;
}
找基准值方法三:快慢指针法
方法:
1.定义两个遍历节点prev current
2.如果current小于left prev++ 如果prev和current不相等交换current++
3.最后交换prev和left的值返回prev
//快慢指针法找基准
private static int quickSlowSort(int[] array, int left, int right) {
int prev = left;
int current = left + 1;
while(current <= right){
if(array[current] < array[left]){
prev++;
if(array[prev] != array[current]){
swap(array,current,prev);
}
}
current++;
}
swap(array,prev,left);
return prev;
}
解决有序数组栈溢出的问题
对于有序数组来说,当使用快速排序时,会出现单向链表的情况,数组容量太大时很可能造成栈溢出的情况,这里我们使用三数取中法来解决
方法:
为了避免基准值处于数组最大最小的情况,在最开始我们就要分析所有可能出现在有序数组的情况,把中间值和数组最开头的值做交换。
虽然三数取中法能解决有序数组栈溢出的情况,但是无序数组用这种方法也不会影响效率。
public static void quickSort(int[]array) {
quickSortProcess(array,0,array.length-1);
}
private static void quickSortProcess(int[] array, int left, int right) {
if(left>=right){
return ;
}
//======快速排序优化 二叉树形式下越往下递归越有序 可以给倒数几层使用插入排序=======//
if(right-left+1<=5){
inSort(array,left,right);
return;
}
//=========三数取中解决有序数列栈溢出问题==========//
int mid=findMid(array,left,right);
swap(array,mid,left);
int pivot = partitionHoare(array,left,right);
partitionHoare(array,left,pivot-1);
partitionHoare(array,pivot+1,right);
}
private static int findMid(int[] array, int left, int right) {
int mid=(left+right)/2;
if(array[left]<array[right]){
if(array[mid]>array[right]){
return right;
}else if(array[mid]<array[left]){
return left;
}else{
return mid;
}
}else{
if(array[mid]<array[right]){
return right;
}else if(array[mid]>array[left]){
return left;
}else{
return mid;
}
}
}
优化方法
针对当数组越往下递归越有序的情况,我们可以使用插入排序进行优化(插入排序在数组基本有序的情况下效率更高)
//插入排序优化快速排序后几层
private static void inSort(int []array,int left,int right){
for(int i=left+1;i<=right;i++){
int j=i-1;
//将i下标的值放在temp中
int temp=array[i];
//i与之前的所有数作比较,如果小继续与前面的作比较,如果大则比较停止
while (j>=left){
//temp的值比j值大则退出循环
if(array[j]<=temp){
break;
}
//j的值比temp的值大则一直把j下标的值往后移
array[j+1]=array[j];
//j往前移
j--;
}
//找到合适位置之后把temp的值放回合适的数组位置
array[j+1]=temp;
}
}
七.归并排序
基本思想
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
方法一(递归):
1.定义mid 求数组中间值 将数组一分为二,并向下递归,直到只剩最后一个元素
2.定义一个新的数组temp,大小为初始元素个数
3.合并数组时,比较每两组元素的值,较小的值先入数组
public static void mergeSort(int[]array){
mergeSortProcess(array,0,array.length-1);
}
private static void mergeSortProcess(int[] array, int left, int right) {
if(left<=right){
int mid=(left+right)/2;
//分解
//分解时第一部分到mid 因为左右两边要分解完
mergeSortProcess(array,left,mid);
mergeSortProcess(array,mid+1,right);
//合并
marge(array,left,mid,right);
}
}
//合并方法
public static void marge(int[] array, int left, int mid, int right) {
//定义新的数组 数组容量是原始数量
int[] temp=new int[right-left+1];
int index=0;
//标记分成两段之后的头和尾
int start1=left;
int end1=mid;
int start2=mid+1;
int end2=right;
//如果每段有元素就把他们放到temp中
while (start1<=end1&&start2<=end2){
//如果第一段的元素小于第二段元素,现放第一段
if(array[start1]<array[start2]){
temp[index++]=array[start1++];
}else{
temp[index++]=array[start2++];
}
}
//比较完成后如果一段元素还没放完全部加入temp末尾
while (start1<=end1){
temp[index++]=array[start1++];
}
while (start2<=end2){
temp[index++]=array[start2++];
}
for (int i=0;i<temp.length;i++){
temp[i]=array[i+left];
}
}
方法二(非递归):
1.定义gap gap*=2 说明每次分为每组gap个元素
2.每两组元素进行递归和合并
//归并迭代
public static void mergeSortItr(int[]array){
int gap=1;
while(gap<array.length){
for (int i=0;i<array.length;i+=2*gap){
int left=i;
int mid=i+gap-1;
int right=mid+gap;
//防止mid和right越界
if(mid>=array.length){
mid=array.length-1;
}
if(right>=array.length){
right=array.length-1;
}
//归并
marge(array,left,mid,right);
}
gap*=2;
}
}
八、排序方法复杂度及稳定性
排序方法 | 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
希尔排序 | O(n) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(n * log(n)) | O(n * log(n)) | O(n * log(n)) | O(1) | 不稳定 |
快速排序 | O(n * log(n)) | O(n * log(n)) | O(n^2) | O(log(n)) ~ O(n) | 不稳定 |
归并排序 | O(n * log(n)) | O(n * log(n)) | O(n * log(n)) | O(n) | 稳定 |