插入排序
插入排序思想:每趟將一個元素,按關鍵字大小插入到它前面已經排序的子序列中,依次重複,知直到插入全部元素。
插入排序分爲直接插入排序、希爾排序、二分插入排序
直接插入排序
在第i趟排序過程中,key = arr[i],arr[0]-arr[i-1]已經有序,從後往前依次將大於key的元素後移,直到遇到小於等於key的元素結束。
- 代碼實現
/**
* 直接插入排序,外層循環i代表待插入元素,內層循環j代表待比較元素*/
public static void straightInsert(int[] nums) {
int len = nums.length;
for (int i = 1; i < len; i++) {
int key = nums[i];
int j = 0;
for (j = i-1; j >= 0; j--) {
if (key < nums[j])
nums[j+1] = nums[j];
else {
break;
}
}
nums[j+1] = key;
}
}
- 效率分析
- 時間複雜度O(n^2),空間複雜度O(1)
- 穩定性分析
- 由於nums[j]>key是循環進行的判斷條件,關鍵字相等的元素會相遇並進行比較,且排序前後順序保持不變,所以直接插入排序是穩定的
希爾排序
將一個數據序列分成若干組,每組由若干相隔一段距離增量的元素組成,在一個組內使用直接插入排序;增量通常設置爲數據序列長度的一半,以後每次減半,直到爲1。
- 代碼實現
/**
* 希爾排序,外層循環delt控制步長,中層循環i代表待插入元素,內層循環j代表待比較元素*/
public static void shellSort(int[] nums) {
int len = nums.length;
for (int delt = len / 2; delt > 0; delt /= 2) {
for (int i = delt; i < len; i++) {
int key = nums[i];
int j = 0;
for (j = i - delt; j >= 0; j -= delt) {
if (key < nums[j])
nums[j+delt] = nums[j];
else {
break;
}
}
nums[j+delt] = key;
}
}
}
- 複雜度分析
- 時間複雜度O(n*(log(n))^2)),空間複雜度O(1)
- 穩定性分析
- 由於增量的存在,希爾排序過程中會錯過相鄰元素的比較,排序算法不穩定。
交換排序
交換排序分爲冒泡排序和快速排序
冒泡排序
- 代碼實現
/**
* 冒泡排序(升序),外層循環控制排序趟數,內層循環控制當前待比較元素*/
public static void bubbleSort(int[] nums) {
int len = nums.length;
for (int i = 1; i < len; i++) {
for (int j = 0; j < len - i; j++) {
if (nums[j] > nums[j+1]) {
int tmp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = tmp;
} else
continue;
}
}
}
- 複雜度分析
- 時間複雜度O(n^2),空間複雜度O(1)
- 穩定性分析
- 冒泡排序由於相鄰元素一定會進行比較,且nums[j] > nums[j+1]作爲判斷條件,所以是穩定的
快速排序
- 代碼實現
/**
* 快速排序算法*/
public static void quickSort(int[] nums) {
quickSort(nums, 0, nums.length - 1);
}
private static void quickSort(int[] nums, int start, int end) {
if (0 <= start && start < end && end < nums.length) {
int front = start; // 初始頭索引
int tail = end; // 初始尾索引
int key = nums[front]; // 選擇當前序列的第一個元素爲參考元素
while (front < tail) {
//從後向前,找到第一個小於參考元素的索引
while (nums[tail] >= key && front < tail) {
tail--;
}
//從後面找到了滿足條件的元素
if (front < tail) {
nums[front++] = nums[tail]; // 移動後面的元素到前面,並更新頭指針
}
//從前向後,找到第一個大於參考元素的索引
while (nums[front] <= key && front < tail) {
front++;
}
//找到了滿足條件的元素
if (front < tail) {
nums[tail--] = nums[front]; // 移動前面的元素到後面,並更新尾指針
}
}
nums[front] = key;
quickSort(nums, start, front - 1);
quickSort(nums, tail + 1, end);
} else
return;
}
- 複雜度分析
- 時間複雜度O(n*log(n)),空間複雜度O(log(n))
- 穩定性分析
- 快速排序算法是不穩定的
選擇排序
選擇排序包含直接選擇排序和堆排序
直接選擇排序
- 基本思想
- 第一趟從n個元素的數據中選擇最小值,放在最前位置;下一趟從n-1個元素中選出最小值,放在次前位置。以此類推,經過n-1趟完成排序。
- 代碼實現
/**
* 直接選擇排序*/
public static void straightSelect(int[] nums) {
int len = nums.length;
//外層循環趟數,每一趟確定一個最小值;內層循環代表待比較元素
for (int i = 0; i < len - 1; i++) {
int min = i; // 存放最小值索引
for (int j = i + 1; j < len; j++) {
if (nums[j] < nums[min])
min = j;
}
//交換
int tmp = nums[i];
nums[i] = nums[min];
nums[min] = tmp;
}
}
- 複雜度分析
- 時間複雜度O(n^2),空間複雜度O(1)
- 穩定性分析
- 直接選擇排序會對不相鄰的元素進行交換,所以是不穩定的
堆排序
- 代碼實現
/**
* 堆排序*/
public static void heapSort(int[] nums) {
int len = nums.length;
//創建最小堆
for (int parent = len / 2; parent >= 0; parent--) {
sift(nums, parent, len - 1);
}
//將根節點和最後一個節點交換位置,nums長度-1,然後再次調整堆
for (int i = len - 1; i > 0; i--) {
//將根節點與最後一個子節點交換
{
int tmp = nums[0];
nums[0] = nums[i];
nums[i] = tmp;
}
//重新調整堆
sift(nums, 0, i - 1);
}
}
private static void sift(int[] nums, int parent, int end) {
int child = parent * 2 + 1; //左孩子
int key = nums[parent]; //當前父節點元素
while (child <= end) { //當前節點存在子節點
//判斷右孩子是否存在,若存在,child指向較小的孩子
if (child + 1 < end && nums[child + 1] < nums[child]) {
child++;
}
//判斷父節點和當前孩子節點的大小關係
if (key > nums[child]) {
//孩子節點上移
nums[parent] = nums[child];
//父節點指針下移
parent = child;
//更新子節點
child = parent * 2 + 1;
} else {
break;
}
}
nums[parent] = key;
}
- 複雜度分析
- 時間複雜度O(n*log(n)),空間複雜度O(1)
- 穩定性分析
- 堆排序不穩定
歸併排序
歸併排序分爲自上而下和自下而上兩種
自上而下
自上而下歸併是一種遞歸的方法,分爲兩種操作:拆分操作、合併操作
- 代碼實現
/**
* 歸併排序算法:自頂向下遞歸排序*/
public class MergeSort2 {
//合併操作,根據索引合併兩個子序列
public static void merge2(int[] x, int start, int mid, int end) {
int[] tmp = new int[end - start + 1];
int index1 = start, index2 = mid + 1, index = 0;
while (index1 <= mid && index2 <= end) {
if (x[index1] < x[index2])
tmp[index++] = x[index1++];
else
tmp[index++] = x[index2++];
}
while (index1 <= mid)
tmp[index++] = x[index1++];
while (index2 <= end)
tmp[index++] = x[index2++];
//移動數組元素
for (int i = 0; i < tmp.length; i++)
x[start + i] = tmp[i];
}
//拆分排序
public static void sort2(int[] x, int start, int end) {
if (start < end) { //遞歸進行的條件
int mid = (start + end) / 2;
sort2(x, start, mid);
sort2(x, mid + 1, end);
merge2(x, start, mid, end);
}
}
public static void mergeSort2(int[] x) {
sort2(x, 0, x.length - 1);
}
}
自下而上歸併
- 代碼實現
/**
* 歸併排序算法:自底向上歸併*/
public class MergeSort {
//遍歷當前數組
public static void printNums(int[] nums) {
int len = nums.length;
StringBuffer sb = new StringBuffer("(");
int index = 0;
for (index = 0; index < len - 1; index++)
sb.append(nums[index] + ",");
sb.append(nums[len - 1]);
sb.append(")\n");
System.out.println(sb.toString());
}
//一次歸併,合併相鄰的子序列
public static void merge(int[] x, int[] tmp, int begin1, int begin2, int n) {
int len = x.length;
int index1 = begin1, index2 = begin2, index = index1; //索引
while (index1 < begin1 + n && index2 < begin2 + n && index2 < len) {
if (x[index1] < x[index2])
tmp[index++] = x[index1++];
else
tmp[index++] = x[index2++];
}
while (index1 < begin1 + n && index1 < len)
tmp[index++] = x[index1++];
while (index2 < begin2 + n && index2 < len)
tmp[index++] = x[index2++];
}
//一趟歸併,合併兩兩相鄰的子序列
public static void mergePass(int[] x, int n) {
int len = x.length;
int[] tmp = new int[len]; //輔助數組
for (int begin = 0; begin < len; begin += 2*n) {
merge(x, tmp, begin, begin + n, n);
}
//移動數組元素
for (int index = 0; index < len; index++)
x[index] = tmp[index];
}
//歸併排序
public static void mergeSort(int[] x) {
int len = x.length;
for (int n = 1; n < len; n *= 2) {
mergePass(x, n);
}
}
}
- 複雜度分析
- 時間複雜度爲O(n*log(n)),空間複雜度爲O(n)
- 穩定性分析
- 歸併排序相鄰元素會進行比較,排序是穩定的。