这里的排序都以升序为例
1.插入排序
public static void insertSort(int[] array) {
for (int bound = 1;bound<array.length;bound++) {
int tmp = array[bound];
int cur = bound-1;
for (;cur>=0;cur--) {
if (array[cur]>tmp) {
array[cur+1] = array[cur];
}else {
break;
}
}
array[cur+1] = tmp;
}
}
从bound号下标元素开始,用一个值tmp记住该元素值,因为自己不需要和自己比较,就从下标为1开始,用bound元素和前面元素进行比较,发现比bound元素大的,就把该元素向后移一位,直到找到比bound元素小的,就把tmp插入到该元素的后面。这要前面就是排好序的区间,后面就是待排区间。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
2.希尔排序
public static void shellSort(int[] array) {
int gap = (array.length)/2;
while (gap>1) {
insertSortGap(array,gap);
gap = gap/2;
}
insertSortGap(array,1);
}
public static void insertSortGap(int[] array,int gap) {
for (int bound = gap;bound<array.length;bound++) {
int tmp = array[bound];
int cur = bound-gap;
for (;cur>=0;cur-=gap) {
if (array[cur]>tmp) {
array[cur+gap] = array[cur];
}else {
break;
}
}
array[cur+gap] = tmp;
}
}
希尔排序是插入排序的优化,就是分组后然后分别对每个组进行插入排序。分组呢就是指定一个数,每隔这个数取一个元素,作为一组,一般的,这个gap取length/2,length/4,length/8.。。。直到gap =1在处理一波。根据插入排序,每次都要从下标为1的元素开始,所以每组的下标为1的元素是原数组下标为gap的元素,然后原本对于插入排序要后移一位的,这里都要移gap位,其他基本都一样。
时间复杂度:O(n^1.3)
空间复杂度:O(1)
稳定性:不稳定
3.选择排序
public static void selectSort(int[] array) {
for (int bound = 0; bound<array.length;bound++) {
for (int cur = bound;cur<array.length;cur++) {
if (array[cur]<array[bound]) {
swap(array,cur,bound);
}
}
}
}
private static void swap(int[] array, int cur, int bound) {
int tmp = array[cur];
array[cur] = array[bound];
array[bound] = tmp;
}
选择排序是从0号下标开始,每次都从后面的元素中选出一个比当前元素小的进行交换,这样最小的就排再了最前面。前面是已排序区间,后面是待排序区间。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
4.堆排序
public static void heapSort(int[] array) {
createHeap(array);
int heapSize = array.length;
for (int i = 0;i<array.length-1;i++) {
swap(array,0,heapSize-1);
heapSize--;
shiftDown(array,heapSize,0);
}
}
private static void createHeap(int[] array) {
for (int i = (array.length-1-1)/2;i>=0;i--) {
shiftDown(array,array.length,i);
}
}
private static void swap(int[] array, int cur, int bound) {
int tmp = array[cur];
array[cur] = array[bound];
array[bound] = tmp;
}
private static void shiftDown(int[] array, int heapSize, int i) {
int parent = i;
int child = 2*parent+1;
while (child<heapSize) {
if (child+1<heapSize && array[child+1]>array[child]) {
child = child+1;
}
if (array[child]>array[parent]) {
swap(array,child,parent);
}else {
break;
}
parent = child;
child = 2*parent+1;
}
}
先用向下调整构造大堆,用一个数表示当前堆得大小,然后进去循环,每次把堆顶元素和最后一个元素进行交换,然后把除最后一个元素外(就是对堆的大小进行减减操作)的堆进行向下调整使其重新变为大堆,这里只要循环length-1就行了,因为当堆剩下最后一个元素时就不需要进行任何操作了。
关于建堆操作,就从最后一个非叶子节点开始向下调整就行了。向下调整,从当前指定下标元素开始,为父节点,左孩子下标为乘以2加一,循环条件为左孩子小标小于当前堆的大小,如果右孩子也小于堆的大小并且比左孩子大,就把右孩子下标给左孩子,保证左孩子一定是最大的,然后和父节点比大小进行适当交换,如果符合大堆要求,直接break就行了,然后把孩子节点下标赋给父节点,新的孩子节点再再新的父节点基础上乘以2加一,进行下次循环。
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定
5.冒泡排序
public static void bubbleSort(int[] array) {
for (int bound = 0;bound<array.length;bound++) {
for (int cur = array.length-1;cur>bound;cur--) {
if (array[cur-1]>array[cur]) {
swap(array,cur-1,cur);
}
}
}
}
冒泡排序,从最后两个元素开始,每次不符合升序,就交换两个值,然后都往前移一位,再比较两个值,这样最小的就到最前面了。,注意cur的循环终止条件,是>,没有等于号。这样前面就是已排序区间,后面是待排序区间。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
6.快速排序
public static void quickSort(int[] array) {
quickSortHelper(array,0,array.length-1);
}
private static void quickSortHelper(int[] array, int left, int right) {
if (left>=right) {
return;
}
int index = partition(array,left,right);
quickSortHelper(array,left,index-1);
quickSortHelper(array,index+1,right);
}
private static int partition(int[] array, int left, int right) {
int baseValue = array[right];
int i = left;
int j = right;
while (i<j) {
while (i<j && array[i]<=baseValue) {
i++;
}
while (i<j&&array[j]>=baseValue) {
j--;
}
if (i<j) {
swap(array,i,j);
}
}
swap(array,i,right);
return i;
}
快速排序的核心操作是:partition
先找一个基准值,一般是最后一个或者第一个,如果找中间的,需要交换到最前面或者最后面。
找最后一个为基准值的话,先从第一个开始从左往右找比基准值大的元素,再从最后一个开始从右往左找比基准值小的元素,然后交换两个值,重复上述操作,知道前面的指针和后面的指针重合,这个数一定是大于基准值的,和基准值进行交换,然后再对重合位置的左右两边递归进行上述操作,完成整个排序。需要注意的是,当你基准值找的是第一个时,就需要先从右往左找,再从左往右找。
我们可以用一个辅助方法,去完成快速排序,传入他要排序的区间,如果区间只有一个或者没有元素,就不用进行处理直接返回,然后进行partition 操作完成第一次排序,并返回新的基准值的位置,接下来对左右区间进行递归处理。
对于partition 方法,我们找最后一个为基准值,然后用i,j分别指向第一个和最后一个元素,判断i,j是否重合,不重合就进入循环,从前找比基准值大的,从后找比基准值小的,进行交换,出循环后,此时,i,j重合,直接和基准值进行交换,最后返回此时基准值的位置。
时间复杂度:最坏-》O(n^2)
平均-》O(nlogn)
空间复杂度:最坏-》O(n)
平均-》O(logn)
稳定性:不稳定
快速排序非递归:
public static void quickSortByLoop(int[] array) {
Stack<Integer> stack = new Stack<>();
stack.push(0);
stack.push(array.length-1);
while (!stack.empty()) {
int right = stack.pop();
int left = stack.pop();
if (left >= right) {
continue;
}
int index = partition(array,left,right);
stack.push(index+1);
stack.push(right);
stack.push(left);
stack.push(index-1);
}
}
private static int partition(int[] array, int left, int right) {
int baseValue = array[right];
int i = left;
int j = right;
while (i < j) {
while (i<j && array[i] <= baseValue) {
i++;
}
while (i<j && array[j] >= baseValue) {
j--;
}
if (i < j) {
swap(array,i,j);
}
}
swap(array,i,right);
return i;
}
private static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
非递归也是参考递归代码进行编写的,依旧需要用到栈,先把需要进行排序的区间进行入栈,如果栈非空进入循环,先后进行两次出栈,如果区间里只有一个元素或者没有,进去下次循环,否则就进行partition操作,再把两边的区间分别入栈。
7.归并排序
public static void mergeSort(int[] array) {
mergeSortHelper(array,0,array.length);
}
private static void mergeSortHelper(int[] array, int left, int right) {
if (right-left <= 1) { //前闭后开区间
return;
}
int mid = (left+right)/2;
mergeSortHelper(array,left,mid);
mergeSortHelper(array,mid,right);
merge(array,left,mid,right);
}
private static void merge(int[] array, int left, int mid, int right) {
int cur1 = left;
int cur2 = mid;
int[] output = new int[right-left];
int outputIndex = 0;
while (cur1 < mid && cur2 < right) {
if (array[cur1] <= array[cur2]) { // <=保证稳定性
output[outputIndex] = array[cur1];
cur1++;
outputIndex++;
}else {
output[outputIndex] = array[cur2];
cur2++;
outputIndex++;
}
}
while (cur1<mid) {
output[outputIndex] = array[cur1];
cur1++;
outputIndex++;
}
while (cur2<right) {
output[outputIndex] = array[cur2];
cur2++;
outputIndex++;
}
for (int i = 0;i<right-left;i++) {
array[left+i] = output[i];
}
}
归并排序,任然需要一个辅助方法去完成排序,传入要排序的区间的参数,此处的区间是前闭后开的,所以判断是否只有一个元素或者没有元素的时候需要right-left<=1去判断,然后依靠中间下标分为两组,在进行后序遍历递归处理,最后的“访问节点操作”就是排序操作再用一个方法去进行实现。
需要传入前中后三个指针参数,left和mid就是分成的两组的首元素,归并需要额外的空间去进行存储,所以需要一个可以足够存储传进来数组的空间,创建一个right-left大小的数组,用一个变量表示当前数组里有几个元素,当两个数组都不为空时,判断left和mid指向的当前元素的大小,把小的传进新建的数组,然后指针向后移动一位,进行比较,最后出循环判断两个数组的其中一个是否还有元素剩余,把剩余的全部加入新建数组里,最后还需要注意把新建数组里的元素全部按顺序再传回到原数组里。这样整个代码就完成了。
时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:稳定