1,按照时间复杂度分类
O(n^2):冒泡排序、选择排序、插入排序
O(n^2~nlogn):希尔排序
O(nlogn):快速排序(不稳定)、堆排序(稳定)、归并排序(稳定,需要额外空间)
O(n):基数排序、计数排序、桶排序
2,快速排序
分为单指针算法和双指针算法,我这里主推双指针,比较简洁;
有一点很重要,快排和归排都属于分而治之的算法,前者重分轻合,后者重合请分;
以下代码,是快速排序的实现,供参考;
package _算法._查找与排序2;
import org.junit.Test;
/***
* 分治法:快排
*
* nlogn
*
* 工业优化:哨兵取三点中值法,规模较小取插入排序
* ***/
public class _快速排序 {
//双指针扫描
private static void qSort(int[] arr, int start, int end) {
int pivot = arr[start]; //哨兵
int i = start;
int j = end;
while (i < j) {
while ((arr[j] > pivot) && (i < j)) { //找出第一个小的值
j--;
}
while ((arr[i] < pivot) && (i < j)) { //找出第一个大的值
i++;
}
if ((arr[i] == arr[j]) && (i < j)) i++; //相等,i++,没必要交换
else {
//交换
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
if (i - 1 > start) qSort(arr, start, i - 1);
if (j + 1 < end) qSort(arr, j + 1, end);
}
//单指针扫描
private static void qSort2(int[] arr, int start, int end){
if (start < end){
int q = partion(arr,start,end);
qSort2(arr,start,q-1);
qSort2(arr,q+1,end);
}
}
private static int partion(int[] arr, int start, int end) {
int pivot = arr[start];
int sp = start+1;//扫描指针
int bigger = end;//尾指针
while (sp<=bigger){
if (arr[sp]>pivot){
int temp = arr[sp];
arr[sp] = arr[bigger];
arr[bigger] = temp;
bigger--;
}
else sp++;
}
int temp = arr[start];
arr[start] = arr[bigger];
arr[bigger] = temp;
return bigger;
}
}
3,归并排序
归并排序重于合,最终子问题为两个元素的比较排序;
package _算法._查找与排序2;
/***
*
* 分治的完美诠释:归并排序
*
* 简分重合
*
* 分:数组中间
* 合:两个有序数组的合并
*
* ***/
public class _归并排序 {
private static void gSort(int[] arr, int[] brr, int start, int end) {
if (start == end) {//出口
return;
}
int mid = start + ((end - start) >> 1);//中点
gSort(arr, brr, start, mid);
gSort(arr, brr, mid + 1, end);
//合并
int left = start;//左指针
int right = mid + 1;//右指针
int k = 0;
while (left <= mid && right <= end) {
if (arr[left] <= arr[right]) {
brr[k++] = arr[left++];
} else {
brr[k++] = arr[right++];
}
}
while (left <= mid && right > end) {
brr[k++] = arr[left++];
}
while (right <= end && left > mid) {
brr[k++] = arr[right++];
}
System.arraycopy(brr, 0, arr, start, end - start + 1);
}
}
4,堆排序
堆排序属于树状排序,因此时间复杂度为nlogn,通过维护最大堆或者最小堆来实现排序的逻辑;
package _算法._查找与排序2;
/***
* 二叉树的结构
* 步骤:1,乱序数组的堆化
* 2,选出堆顶,数组减1
* 3,调整堆
* 反复
* ***/
public class _堆排序 {
private static void heapSort(int[] arr) {
buildMinHeap(arr);//建最小堆
selectTopToEnd(arr, arr.length - 1);//选取堆顶
reserveArr(arr);
}
private static void reserveArr(int[] arr) {
//顺序反转
for (int i = 0, j = arr.length - 1; i < j; i++, j--) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
private static void selectTopToEnd(int[] arr, int end) {
if (end == 0) return;
int temp = arr[0];//交换最后与堆顶元素
arr[0] = arr[end];
arr[end] = temp;
ReBuildMinHeap(arr, 0, end - 1, false);//重新调整堆
selectTopToEnd(arr, end - 1);//递归实现
}
private static void buildMinHeap(int[] arr) {
for (int i = arr.length / 2 - 1; i >= 0; i--) {
ReBuildMinHeap(arr, i, arr.length - 1, true);//全局调整堆
}
}
/***
* @param arr:待排序数组
* @param i:排序根节点起点
* @param end:数组长度
* @param first: 是否第一次建堆
* ***/
private static void rebuildMinHeap(int[] arr, int i, int end, boolean first) {
int left = 2 * i + 1;//左节点
int right = 2 * i + 2;//右节点
if (left > end) return;//出口
if (right <= end) {//有两子节点时
//选最小元素的下标
int min = arr[i] > arr[left] ? left : i;
min = arr[min] > arr[right] ? right : min;
if (min == i) {
if (!first) return;//不用调整了
rebuildMinHeap(arr, left, end, true);
rebuildMinHeap(arr, right, end, true);
}
//交换
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
if (min == left) rebuildMinHeap(arr, left, end, first);
else rebuildMinHeap(arr, right, end, first);
} else {//只有一个节点
int min = arr[i] > arr[left] ? left : i;
if (min == i) return;
//交换
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
}
5,计数排序
典型的空间换时间做法,一般用于元素区间不大的场合,比如高考成绩分数排序;
package _算法._查找与排序2;
import java.util.Arrays;
/***
* 简知:牺牲空间换取时间的排序算法
* ***/
public class _计数排序 {
private static void countSort(int[] arr) {
int max = max(arr);
int[] helper = new int[max + 1];
Arrays.fill(helper,0);
for (int m : arr) {
helper[m]++;
}
for (int i = 0, k = 0; i < helper.length; ) {
if (helper[i] == 0) {
i++;
continue;
}
arr[k++] = i;
helper[i]--;
}
}
private static int max(int[] arr) {
int max = 0;
for (int m : arr) {
if (max < m) max = m;
}
return max;
}
}
6,桶排序
一种优化的计数排序,当元素区间很大时,可以分区间排序,区间内维护有序链表;
package _算法._查找与排序2;
import java.util.Arrays;
/***
* 创建桶,个数为n(数组长度),通过某算法,将数组元素入桶,如何取出。
* **/
//创建链表
class Link {
private Link next = null;//下一指针
private int value = 0;
Link(int value) {
this.value = value;
}
Link() {
//无参构造方法,head
}
void insertNextKeepSort(Link next) {
//插入新元,且保证有序
Link t = this;//如果head后没有元素
if (t.next == null) {
t.next = next;
return;
}
Link p = t;//前指针
t = t.next;
while (t.value > next.value && (t.next != null)) {
p = t;
t = t.next;
}
if ((t.next == null) && (t.value > next.value)) {
t.next = next;//最后了
return;
}
p.next = next;
next.next = t;
}
int popValue() {
//出元
Link t = this;
Link p = this;
while (t.next != null) {
p = t;
t = t.next;
}//移到最后一个
int temp = t.value;
p.next = null;//释放
return temp;
}
boolean hasNext() {
//如果桶内还有元素
return this.next != null;
}
}
public class _桶排序 {
private static void tSort(int[] arr) {
int max = max(arr);
Link[] link = new Link[arr.length + 1];
for (int i = 0; i < link.length; i++) {
link[i] = new Link();
}
int index;
for (int m : arr) {
index = m * arr.length / (max + 1);//分桶
link[index].insertNextKeepSort(new Link(m));
}
int k = 0;
for (Link m : link) {
while (m.hasNext()) {
arr[k++] = m.popValue();
}
}
}
private static int max(int[] arr) {
int max = 0;
for (int m : arr) {
if (m > max) max = m;
}
return max;
}
}