目录
一、冒泡排序
最基础的排序,思路是将数据和下一个数据进行比较,如果比下一个大就交换,然后指向下一个元素,依次类推则最大的元素被移到了最后。然后继续进行下一轮排序,直到排序完成。利用减治的思想。
public static void bubbleSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
boolean flag=true;
for (int j = 0; j < arr.length-i-1; j++) {
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
flag=false;
}
}
if(flag){
return;
}
}
}
private static void swap(int[] arr,int i, int i1) {
int t=arr[i];
arr[i]=arr[i1];
arr[i1]=t;
}
二、选择排序
选择排序,思路是每次都选择出最小的元素放在有序区间的后面。相比于冒泡每次大的都要交换,选择排序是每次找出最小的进行交换。代码如下:
public static void selectionSort1(int[] arr){
for (int i = 0; i < arr.length; i++) {
int minindex=i;
for (int j = i+1; j < arr.length ; j++) {
if(arr[j]<arr[minindex]){
minindex=j;
}
}
swap(arr,minindex,i);
}
}
private static void swap(int[] arr, int maxindex, int i) {
int t=arr[maxindex];
arr[maxindex]=arr[i];
arr[i]=t;
}
三、插入排序
插入排序,就是每次将无序区间的第一个元素和有序区间的所有元素进行比较,如果大就不用移动,如果小就向前移,直到找到合适的位置。
public static void insertSort(int [] arr){
for (int i=0;i<arr.length-1;i++){
for (int j = i; j >=0 ; j--) {
if(arr[j+1]>arr[j]){
break;
}else{
swap(arr,j,j+1);
}
}
}
}
private static void swap(int[] arr, int j, int i) {
int t=arr[j];
arr[j]=arr[i];
arr[i]=t;
}
四、希尔排序
希尔排序是在插排的基础上,插排每次都是一个一个的进行排序,希尔排序每次都间隔一定数量来进行插排,这个间隔不断减小,直到最后一次使用相邻元素进行比较,也就是插排。
public static void shellSort(int[] arr){
int gap=arr.length/2;
while (gap>1){
insertSortWithGap(arr,gap);
gap=gap/2;
}
insertSortWithGap(arr,1);
}
public static void insertSortWithGap(int[] arr,int gap){
for (int i = 0; i < arr.length-gap; i++) {
for (int j = i; j >=0 ; j=j-gap) {
if(arr[j+gap]>arr[j]){
break;
}else {
swap(arr,j+gap,j);
}
}
}
}
private static void swap(int[] arr, int i, int j) {
int t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
五、堆排序
堆排序,利用大堆的特点,即最大的元素总是在根节点处。所以我们可以将待排序元素组建成为一个大堆,然后交换最后一个元素和根节点,然后进行向下调整,然后继续交换,直到排序完成。
public static void heapSort(int [] arr){
int [] array=creatHeap(arr);
for (int i = 0; i < arr.length-1; i++) {
swap(arr,0,array.length-1-i);
shiftDown(array,array.length-1-i,0);
}
}
private static void shiftDown(int[] array, int size, int index) {
while (index*2+1<size){
int max=index*2+1;
int right=max+1;
if(right<size&& array[max]<array[right]){
max=right;
}
if(array[index]>=array[max]){
return;
}
swap(array, max, index);
index=max;
}
}
private static void swap(int[] arr, int i, int j) {
int t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
private static int[] creatHeap(int[] arr) {
for (int i = (arr.length-2)/2; i >=0 ; i--) {
shiftDown(arr, arr.length,i);
}
return arr;
}
六、归并排序
归并排序利用分治的思想,就是将无序区间划分为两个无序区间,然后再继续划分,当元素个数小于等于一的时候自然有序。然后将有序区间进行合并。直到最后完成合并,排序完成。
private static void mergeSort(int[] array){
mergeSortRange(array,0,array.length);
}
private static void mergeSortRange(int[] array, int from, int to) {
int size=to-from;
int mid=from+(size/2);
if (size<=1){
return;
}
mergeSortRange(array, from, mid);
mergeSortRange(array, mid,to);
merge(array,from,mid,to);
}
private static void merge(int[] array, int from, int mid, int to) {
int left=from;
int right=mid;
int dest=0;
int size=to-from;
int[] other=new int[size];
while (left<mid && right<to){
if(array[left]<=array[right]){
other[dest]=array[left];
dest++;
left++;
}else{
other[dest++]=array[right++];
}
}
//一定还有一个区间的元素还没有取完。所以一定要补上
while (left<mid){
other[dest++]=array[left++];
}
while (right<to){
other[dest++]=array[right++];
}
//此时我们需要将元素复制回去
for (int i = 0; i < size; i++) {
array[from+i]=other[i];
}
}
七、快速排序
快速排序作为我们的重量级选手登场了。快速排序也是利用分治的思想,每次选取一个pivot,然后通过partition操作将小于pivot的元素放在其左边,大于pivot的元素放在右边,只要将左边和右边排序好就完成了。所以我们将问题化为子问题。然后递归调用,直到完成排序。所以问题的关键在于partition的完方法。
这是介绍的第一种parttition方法。
public static void quickSort(int[] arr) {
quickSortRange(arr, 0, arr.length - 1);
}
public static void quickSortRange(int[] arr, int from, int to) {
if (to <= from) {
return;
}
int pi = partitionMethodA(arr, from, to);
quickSortRange(arr, from, pi - 1);
quickSortRange(arr, pi + 1, to);
}
private static int partitionMethodA(int[] array, int from, int to) {
int pivot = array[to];
int left = from;
int right = to;
//循环结束的条件就是left=right的时候。此时所有元素都被比较过了
while (left < right) {
//为了确保内层循环在变动的时候左指针超越右指针,需要在前面加上限制条件
while (left < right && array[left] <= pivot) {
left++;
}
//上面的这个循环结束后,表明此时left指针到了一个比pivot大的值处
while (left < right && array[right] >= pivot) {
right--;
}
//上面的这个循环结束后,表明此时right指针到了一个比pivot小的值处
//此时需要做的就是将左右指针的值进行交换,然后继续开始比较
int t = array[left];
array[left] = array[right];
array[right] = t;
}
//当所有的值比较完毕之后,现在left==right的值,此时我们的pivot在to位置,只需交换位置即可。
int t = array[to];
array[to] = array[left];
array[left] = t;
//交换位置后的pivot的下标为left,将其返回。以便下次递归调用。
return left;
}
“挖坑法”
private static int partitionMethodB(int[] arr, int from, int to) {
int pivot = arr[to];
int left = from;
int right = to;
while (left < right) {
while (left < right && arr[left] <= pivot) {
left++;
}
arr[right] = arr[left];
//同样的,我们只是换一个方法来完成交换。假设此时的right(to,pivot)为坑,如果遇到不满足left<=pivot的就将该处的值填入坑中,同时坑的位置变成left
//坑的作用就是我们不用进行复杂的swap(),而是记录位置。
while (left < right && arr[right] >= pivot) {
right--;
}
arr[left] = arr[right];
//此时坑的位置是left,所以将right的值填入left坑中
}
arr[left] = pivot;
//循环结束后我们只需要将提前记录的pivot填入坑中即可。
return left;
}
//以上方法AB的两种思路本质上是一样的,只不过是第二种利用了小技巧使得交换次数变少了
遍历法:
private static int partitionMethodC(int[] array, int from, int to) {
//利用遍历的方法,s的左边是小于pivot的值,s右边(包括s)是大于pivot的值。遍历区间是从左往右,直到遍历完成,交换s和最后的pivot即可
int s = from;
int pivot = array[to];
for (int i = from; i < to; i++) {
if (array[i] < pivot) {
//当遇见比pivot小的值的时候,需要让这个值到s的前面,所以我们交换i和s的值,然后将后移一位。
int t = array[i];
array[i] = array[s];
array[s] = t;
s++;
}
//遇见大的值不用管,继续遍历即可。
}
//最后遍历结束之后将pivot的值和s处的值进行交换即可。
array[to] = array[s];
array[s] = pivot;
return s;
}
第四种方法将以上方法进行结合
public static void quickSort1(int[] arr) {
quickSortRange(arr, 0, arr.length - 1);
}
public static void quickSortRange1(int[] arr, int from, int to) {
if (to <= from) {
return;
}
int[] indices = partitionMethodD(arr, from, to);
int less=indices[0];
int great=indices[1];
quickSortRange(arr, from, less);
quickSortRange(arr, great, to);
}
private static int[] partitionMethodD(int[] array, int from, int to){
//此方法将区间分割为3块分别是小于等于和大于三部分。其中s是区分小于和等于的边界。i相等于左指针,g相当于右指针。
int s=from;
int i=from;
int g=to;
int pivot=array[to];
//当g>=i是比较还未完成
while (g>=i){
//当i的值等于pivot时,继续遍历不进行其他操作。
if(array[i]==pivot){
i++;
}
//当i的值小于pivot时,和C方法一样,交换并且后移即可
else if(array[i]<pivot){
swap(array,i,s);
s++;
i++;
}
//当i的值大于pivot时,交换i和g的值,此时的i不需要++,因为交换过来的值还没有比较,但是g是交换后的值,前移即可
else {
swap(array,i,g);
g--;
}
}
//返回s的前一个位置和i的位置,方便下次递归调用
return new int[]{s-1,i};
}
private static void swap(int[] array, int i, int s) {
int t=array[i];
array[i]=array[s];
array[s]=t;
}
八、分析
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
冒泡排序 | 稳定 | ||||
选择排序 | 稳定 | ||||
插入排序 | 稳定 | ||||
希尔排序 | ~ | 不稳定 | |||
堆排序 |
| 不稳定 | |||
归并排序 |
| 稳定 | |||
快速排序 |
| ~ | 不稳定 |
总结
我们一定要熟记这几种排序的思想,尤其是快速排序,还要会分析它们的优点和缺点,使用的情况等。