排序算法
常用排序算法的理解及示例代码:冒泡排序、插入排序、选择排序、归并排序、快速排序、二叉树排序
分析算法
- 内存消耗:是否是原地排序算法。指空间复杂度为 O(1) 的算法
- 稳定性:是否是稳定的排序算法。等值元素排序后次序不变,为稳定的排序算法
- 执行效率:时间复杂度,最好、最坏、平均或均摊
冒泡排序
相邻元素根据大小交换位置,每次遍历将一个元素放到合适的位置上
- 是否原地排序:原地排序
- 是否稳定:控制相邻元素大小相等时不做交换,则为稳定排序
- 时间复杂度:\(O(n^2)\)
- 设置标志位,优化算法,避免已经有序后还在做无用循环
public static void bubble(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
boolean traverse = true;//是否还需要遍历的标志
for (int j = 0; j < a.length - i - 1; j++) {
if (a[j] > a[j + 1]) {
traverse = false;
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
if (traverse) break;
}
}
}
插入排序
总是取无序区第一个元素,有序区由大向小与该元素比较,比该元素大则后移一位,最终让出合适位置
- 是否原地排序:原地排序
- 是否稳定:控制后面出现的元素插入到前面相同元素的后面,则为稳定排序
- 时间复杂度:\(O(n^2)\)
public static void insert(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
int x = a[i + 1];
int j = i;
for (; j >= 0; j--) {
if (a[j] > x) a[j + 1] = a[j];//比 x 大后移一位
else break;//找到合适位置
}
a[j + 1] = x;//循环中多减了一次 1
}
}
选择排序
依次遍历每个位置,与后续元素比较,比该位小的换到该位
- 是否原地排序:原地排序
- 是否稳定:不是稳定排序
- 时间复杂度:\(O(n^2)\)
public static void select(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[i]) {
int temp = a[j];
a[j] = a[i];
a[i] = temp;
}
}
}
}
归并排序
原数组分成两部分,递归得到 length 个数组,每个数组中一个元素
两两数组合并,递归得出排序结果
- 是否原地排序:非原地排序,空间复杂度较高,\(O(n)\)
- 是否稳定:稳定
- 时间复杂度:\(O(nlogn)\)
public static void sort(int[] a, int left, int right) {
if (left >= right) return;
int mid = left + (right - left >> 2);//取中间元素
sort(a, left, mid);
sort(a, mid + 1, right);
merge(a, left, mid, right);
}
public static void merge(int[] a, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int l = left, r = mid + 1, count = 0;
// 比较两部分放入大小,小元素放入数组
while (l <= mid && r < right) {
temp[count++] = a[l] < a[r] ? a[l++] : a[r++];
}
// 两部分个数不一样时,将长的部分剩余元素加入数组
while (l > mid && r <= right) temp[count++] = a[r++];
while (l <= mid && r > left) temp[count++] = a[l++];
// 复制给原数组
for (int i = 0; i < temp.length; i++) {
a[left + i] = temp[i];
}
}
快速排序
设置头尾指针,选取一个元素作为中点
先从尾指针向前找比中点小的,移到头指针,头指针后移一位
再从头指针向后找比中点大的,移到尾指针,尾指针前移一位
指针相遇时,得到中点位置,保证了该位置左边元素小,右边元素大
递归执行头结点到中点 -1、中点 +1 到尾结点两段元素的排序
- 是否原地排序:原地排序
- 是否稳定:不稳定
- 时间复杂度:\(O(nlogn)\)
public static void quick(int[] a, int left, int right) {
if (left >= right) return;
int i = left, j = right;
int x = a[left];
while (i < j) {
while (i < j && a[j] >= x) j--;
if (i < j) a[i++] = a[j];
while (i < j && a[i] <= x) i++;
if (i < j) a[j--] = a[i];
}
a[i] = x;
quick(a, left, i - 1);
quick(a, i + 1, right);
}
二叉树排序
左小右大生成二叉树,中序遍历得出的结果有序
- 是否原地排序:非原地排序
- 是否稳定:控制相等时放在右结点,则为稳定排序
- 时间复杂度:\(O(n)\)
public static void treeSort(int[] a) {
TreeNode tree = new TreeNode();
for (int i : a) {//生成树
tree.add(i);
}
List list = tree.inOrder();//中序遍历
for (int i = 0; i < list.size(); i++) {
a[i] = list.get(i);//装回数组
}
}
static class TreeNode {//静态内部类,可在静态方法中实例化
TreeNode left, right;
Object value;
// 左小右大生成二叉树树
public void add(Object o) {
if (value == null) {
value = o;
} else {
if ((int) o < (int) value) {
left = (left == null) ? new TreeNode() : left;
left.add(o);
} else {
right = (right == null) ? new TreeNode() : right;
right.add(o);
}
}
}
// 中序遍历得出的结果有序
public List inOrder() {
List list = new ArrayList<>();
if (left != null) list.addAll(left.inOrder());
list.add((int) value);
if (right != null) list.addAll(right.inOrder());
return list;
}
}