文章目录
1. 交换顺序
void Swap(int *a, int *b) {
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
2. 排序算法分析
- 如何判断排序算法是否是稳定的?
- 如果在排序过程中,没有发生跳跃式的交换,那么该排序算法就是稳定的。
对常见的排序算法进行分类:
- 稳定的排序
- 冒泡排序
- 插入排序
- 归并排序
- 不稳定的排序
- 选择排序
- 堆排序
- 快速排序
- 希尔排序
2.1 快速排序
2.1.1 算法思想
以从小到大为例,选取基准数,把比基准数小的放到左边,反之放到右边,反复执行这一步骤,直到只剩下一个元素为止。
- 挖坑法:假设取 最右边的数字 作为 基准数,先保存一份基准数的值,然后从左边找比基准数大的数字 begin_index,覆盖基准数所在元素,从右边出发,向左找比基准数小的数字 end_index,覆盖掉之前的 begin_index 的元素,直到 begin_index 与 end_index 相遇,最后把 保存的基准数填到 相遇位置即可。
2.1.2 代码实现
int cutPart(int arr[], int left, int right) {
int tmp = arr[right];
int begin = left;
int end = right;
while (begin < end) {
while ((begin < end) && (arr[begin] <= tmp)) {
begin++;
}
arr[end] = arr[begin];
while ((begin < end) && (arr[end] >= tmp)) {
end--;
}
arr[begin] = arr[end];
}
arr[begin] = tmp;
return begin;
}
void quickRecursion(int arr[], int left, int right) {
if (left >= right)
return;
int mid = cutPart(arr, left, right);
quickRecursion(arr, left, mid - 1);
quickRecursion(arr, mid + 1, right);
}
void quickSort(int arr[], int len) {
quickRecursion(arr, 0, len - 1);
}
2.1.3 算法分析
最坏时间复杂度是在已经有序的情况下发生的。
- 最坏时间复杂度:
O(N^2)
,平均时间复杂度:O(NlogN)
- 划分的层数为
O(logN)
或者O(N)
- 每一层划分的时间复杂度为
O(N)
- 划分的层数为
- 空间复杂度:
O(logN)
- 稳定性:
不稳定
(会发生跳跃式的交换数据)
2.2 归并排序
2.2.1 算法思想
以从小到大为例,将数组中的元素切分,一次分两半,直到只剩下一个数字时,然后开始合并。
2.2.2 代码实现
void merge(int arr[], int *extraArr, int left, int mid, int right) {
int pos = left;
int l_pos = left;
int r_pos = mid + 1;
while (l_pos <= mid && r_pos <= right)
extraArr[pos++] = arr[l_pos] <= arr[r_pos] ? arr[l_pos++] : arr[r_pos++];
while (l_pos <= mid)
extraArr[pos++] = arr[l_pos++];
while (r_pos <= right)
extraArr[pos++] = arr[r_pos++];
while (left <= right) {
arr[left] = extraArr[left];
left++;
}
}
void mergeRecursion(int arr[], int *extraArr, int left, int right) {
if (left >= right)
return;
int mid = (left & right) + ((left ^ right) >> 1);
mergeRecursion(arr, extraArr, left, mid);
mergeRecursion(arr, extraArr, mid + 1, right);
merge(arr, extraArr, left, mid, right);
}
void mergeSort(int arr[], int len) {
int *extraArr = (int *)malloc(sizeof(int) * len);
if (NULL == extraArr) {
perror("malloc fail");
exit(EXIT_FAILURE);
}
mergeRecursion(arr, extraArr, 0, len - 1);
free(extraArr);
extraArr = NULL;
}
2.2.3 算法分析
- 时间复杂度:
O(NlogN)
- 每一层的归并的时间复杂度为
O(N)
- 归并的层数为
O(logN)
- 每一层的归并的时间复杂度为
- 空间复杂度:
O(N)
- 稳定性:
稳定
2.3 插入排序
2.3.1 算法思想
第一个数不需要排序,因此比较趟数
i
为[1, size - 1]
或者[1, size)
,j
从i - 1
这个位置开始比较(从后往前找),最后比较到下标为0
的位置或者第一个不大于array[i]
的数,比array[i]
大的数都向后移动一格,这样其实就相当于空出来一格,如果找到不大于array[i]
的数,就把array[j]
放到该位置的下一个位置。
2.3.2 代码实现
void insertSort(int arr[], int len) {
/*int i = 1;
for (; i < len; ++i) {
int j = i - 1;
for (; j >= 0 && arr[j] > arr[j + 1]; j--) {
Swap(&arr[j], &arr[j + 1]);
}
}*/
int i = 1;
for (; i < len; ++i) {
int key = arr[i];
int j = i - 1;
for (; (j >= 0) && (arr[j] > key); j--) {
arr[j + 1] = arr[j];
}
arr[j + 1] = key;
}
}
2.3.3 算法分析
- 时间复杂度:最好(有序)
O(N)
,最坏(逆序)O(N^2)
- 空间复杂度:
O(1)
- 稳定性:
稳定
2.4 冒泡排序
2.4.1 算法思想
以升序为例,比较相邻的两个数字,若前者大于后者,则交换两个数字的顺序,反之则不交换。重复上述操作,直到最大的数字
沉
入最右边,最小的数字浮
在最左边为止。
假如有 N
个数字排序,那么需要比较 N - 1
趟,在每一趟的比较中,比较次数有所不同,第 i
趟需要比较 N - 1 - i
次。
2.4.2 代码实现
#define TRUE 1
#define FALSE 0
void bubbleSort(int arr[], int len) {
int i = 0;
int j = 0;
int is_swap = TRUE;
for (; is_swap && (i < len - 1); ++i) {
j = 0;
is_swap = FALSE;
for (; j < len - 1 - i; ++j) {
if (arr[j] > arr[j + 1]) {
Swap(arr + j, arr + j + 1);
is_swap = TRUE;
}
}
}
}
2.4.3 算法分析
- 时间复杂度:最好(有序)
O(N)
,最坏(逆序)O(N^2)
- 空间复杂度:
O(1)
- 稳定性:
稳定
2.5 选择排序
2.5.1 算法思想
以升序为例,用
min
表示最小值的下标,从第0
个下标开始,比较第一趟,若发现某个值小于第0
个下标的值,那么就把min
置为当前下标,每趟比较结束后,比较min
和i
的值,如果发现不一样了,就将这两个下标对应的值交换顺序,执行size - 1
趟比较后,选择排序完成。
2.5.2 代码实现
void selectSort(int arr[], int len) {
int i = 0;
int j = 0;
int min = 0;
for (; i < len - 1; ++i) {
min = i;
for (j = i + 1; j < len; ++j) {
min = arr[j] < arr[min] ? j : min;
}
if (min != i) {
Swap(arr + min, arr + i);
}
}
}
2.5.3 算法分析
- 时间复杂度:
O(N^2)
- 空间复杂度:
O(1)
- 稳定性:
不稳定
2.6 希尔排序
2.6.1 算法思想
本质上还是插入排序,只是加入了
gap
这一变量,使得数组中的元素 先分组然后插入排序,直到gap
减为1
,也叫做缩小增量排序
。
2.6.2 代码实现
void shellSort(int arr[], int len) {
int i = 0;
int j = 0;
int gap = len;
while (gap > 1) {
gap = gap / 3 + 1;
for (i = gap; i < len; ++i) {
int key = arr[i];
for (j = i - gap; j >= 0 && arr[j] > key; j -= gap)
arr[j + gap] = arr[j];
arr[j + gap] = key;
}
}
}
2.6.3 算法分析
- 时间复杂度:最坏
O(N^2)
,平均时间复杂度O(N^1.3)
- 空间复杂度:
O(1)
- 稳定性:
不稳定
2.7 堆排序
2.7.1 算法思想
先建立大堆(从最后一个有孩子节点的父节点开始向前建立大堆),然后交换最后一个元素和堆顶元素,从堆顶开始向下调整堆,接着让倒数第二个与堆顶元素交换,再向下调整堆,直到只剩下堆顶元素,这样就可以依次得到最大的元素、次大的元素…
2.7.2 代码实现
void AdjustDownRecursion(int arr[], int len, int index) {
int maxIndex = index;
int left = index * 2 + 1;
int right = index * 2 + 2;
if ((left < len) && (arr[maxIndex] < arr[left]))
maxIndex = left;
if ((right < len) && (arr[maxIndex] < arr[right]))
maxIndex = right;
if (maxIndex != index) {
Swap(arr + maxIndex, arr + index);
AdjustDownRecursion(arr, len, maxIndex);
}
}
void buildHeap(int arr[], int len) {
int i = 0;
// 从后往前 建堆
for (i = (len - 1 - 1) / 2; i >= 0; --i) {
AdjustDownRecursion(arr, len, i);
}
}
void heapSort(int arr[], int len) {
int i = 0;
buildHeap(arr, len);
for (i = len - 1; i > 0; --i) {
Swap(arr + i, arr);
// 向下调整
AdjustDownRecursion(arr, i, 0);
}
}
2.7.3 算法分析
- 建堆复杂度为:
O(N)
- 时间复杂度:最坏
O(NlogN)
- 空间复杂度:
O(1)
- 稳定性:
不稳定
(堆顶元素与最后一个元素交换)