内排序,指记录数较少,可以在内存中进行的排序。
排序算法稳定性,排序关键字相同的记录拍完序顺序保持不变,这个算法就是稳定的。
生成一个随机数组
void mkarr(int **arr, int size) {
*arr = (int*)malloc(sizeof(int)*size);
srand(time(NULL));
for (int i = 0; i < size; i++)
*(*arr + i) = rand();
}
void checkSorted(int *arr, int size, int flag) {
if (flag == 0) {
for (int i = 0; i < size - 1; i++)
if (arr[i] > arr[i + 1]) {
printf("error index[%d], value1[%d], value2[%d]\n", i, arr[i], arr[i + 1]);
return;
}
}
else {
for (int i = 0; i < size - 1; i++)
if (arr[i] < arr[i + 1]) {
printf("error index[%d], value1[%d], value2[%d]\n", i, arr[i], arr[i + 1]);
return;
}
}
printf("all right\n");
}
假设数组的前i个数已经排好序,然后将第i+1个数插入到前i个中,只到n个数都插入到自己的位置。
平均时间复杂度 O(n2),当序列已经排好序时时间复杂度低O(n),当序列是逆序时时间复杂度最高为O(n2)。插入过程都是从后往前,所以算法是稳定的。
void insertSort(int *arr, int size) {
for (int i = 1; i < size; i++) {
int tmp = arr[i]; // 保存第i个元素
int j = i - 1;
for (j = i - 1; j >= 0 && tmp < arr[j]; j--) {
arr[j + 1] = arr[j]; // 比第i个元素小,向后移一位
}
arr[j + 1] = tmp;
}
}
假设前i-1个元素已经排好序,从第i到n个元素中选择最小的放到第i个位置,只到第n个元素。
因为每次选择都要把剩下的元素全部检查一遍所以,算法的最低、最高、平均时间复杂度都是O(n2)。选择排序在第一轮的最小元素是最后一个,这样第一个元素就直接被交换到最后,如果序列中有和第一个元素相同的元素,那他们的相对位置肯定变了,所以算法是不稳定的。
void selectionSort(int *arr, int size) {
for (int i = 0; i < size; i++) {
int min = i;
int j = i;
for (; j < size; j++) {
if (arr[min] > arr[j]) min = j;
}
int tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
从前到后相邻的元素比较大小,如果前面的元素大于后面的,两个元素交换,如果没有大于,下一个元素继续与相邻的下一个元素进行比较,每一轮可以确定一个最大的元素。
每一轮冒泡都要把所有相邻元素进行比较,所以算法的最低、最高、平均时间复杂度都是O(n2)。冒泡方向不变,所以算法是稳定的。
void bubbleSort(int *arr, int size) {
int tmp;
for (int i = 0; i < size; i++) {
for (int j = 0; j < size - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp;
}
}
}
}
选择一个基准元素对数组进行分割,比基准大的在一边,比基准小的在一边,分割结束后基准元素已经排好序。再对左右两个部分递归进行分割,直到整个数组有序。
平均时间复杂度O(nlogn),当算法每次选择基准值时都选择到最大或最小元素时,算法时间复杂度最高O(n2),每次都选到中间值时时间复杂度最低为O(nlogn)。划分的过程是从两边向中间走,所以快速排序不稳定。
// 划分数组,返回基准值位置
int parition(int *arr, int size) {
int pivot = 0;
int tmp = arr[pivot];
int low = 0, high = size - 1;
while (low < high) {
while (arr[high] > tmp && low < high)
high--;
if (low < high) {
arr[pivot] = arr[high]; pivot = high;
}
while (arr[low] < tmp && low < high)
low++;
if (low < high) {
arr[pivot] = arr[low]; pivot = low;
}
}
arr[pivot] = tmp;
return pivot; //返回基准元素的位置
}
void quickSort(int *arr, int size) {
if (size <= 1) return;
if (size == 2 && arr[0] > arr[1]) {
int tmp = arr[0]; arr[0] = arr[1]; arr[1] = tmp;
}
int pivot = parition(arr, size);
quickSort(arr, pivot);
quickSort(arr + pivot + 1, size - pivot - 1);
}
把两个相对有序的子序列合并成一个.
时间复杂度 O(nlogn),空间复杂度 n。两个序列归并的过程相同排序字段的元素不会发生交换,所以是稳定的。
// 合并两个子序列
void merge(int *arr, int *res, int start1, int end1, int start2, int end2) {
int ix = start1;
// 先复制到临时数组,然后再从临时数组往回合并
for (int i = start1; i <= end2; i++) res[i] = arr[i];
while (start1 <= end1 && start2 <= end2) {
if (res[start1] <= res[start2])
arr[ix++] = res[start1++];
else
arr[ix++] = res[start2++];
}
while (start1 <= end1)
arr[ix++] = res[start1++];
while (start2 <= end2)
arr[ix++] = res[start2++];
}
void mergeSort(int *arr, int *res, int start, int end) {
if (start >= end)
return;
mergeSort(arr, res, start, (end + start) / 2);
mergeSort(arr, res, (end + start) / 2 + 1, end);
merge(arr, res, start, (end + start) / 2, (end + start) / 2 + 1, end);
}
对数组的前n个元素建堆,将第一个元素与第n个元素交换,然后对前n-1元素建堆.
重新建堆的时间复杂度为O(logn),所以算法的最好、最坏、平均时间复杂度都O(nlogn)。建堆过程中排序字段相同元素的父节点可能不相同,导致相对顺序有可能改变,所以堆排序不稳定。
void heapSort(int* arr, int start, int end) {
int tmp;
// 对从start 到end 建立大根堆
while (end > start) {
for (int i = (start + end - 1) / 2; i >= 0; i--) {
if (arr[i] < arr[i * 2 + 1]) {
tmp = arr[i]; arr[i] = arr[i * 2 + 1]; arr[i * 2 + 1] = tmp;
}
if (i * 2 + 2 <= end && arr[i] < arr[i * 2 + 2]) {
tmp = arr[i]; arr[i] = arr[i * 2 + 2]; arr[i * 2 + 2] = tmp;
}
}
// 最大的元素与最后一个元素交换,然后继续对start到end-1建立大根堆
tmp = arr[start]; arr[start] = arr[end]; arr[end] = tmp;
end--;
}
}
先用增量x对原数组进行划分,(0,x,2*x…)/(1,1+x,1+2*x…)/(2,2+x,2+2*x…)…
然后在每个划分上执行插入排序,然后增量减半x=x/2,继续执行,知道x=1;
与插入排序相同,当原序列有序是时间复杂度最低为O(n),平均和最高时间复杂度都为O(n2)。相同排序关键字的元素可能会划入不同的子序列,导致相对位置变化,所以shell排序是不稳定的。
void shellInsertSort(int *arr, int start, int end, int step) {
int tmp; int j;
for (int i = start + step; i <= end; i++) {
tmp = arr[i];
for (j = i - step; j >= start && arr[j] > tmp; j -= step)
arr[j + step] = arr[j];
arr[j + step] = tmp;
}
}
void shellSort(int* arr, int start, int end) {
int tmp = 0;
for (int i = (start + end + 1) / 2; i >= 1; i /= 2) { // 步长从 序列长度的二分之一开始, 等于一时变成和普通的插入排序一样
for (int j = start; j < i; j++) {
// 在每个划分上执行插入排序
shellInsertSort(arr, j, end, i);
}
}
}
时间复杂度O(n)
int linerSearch(int *arr, int target, int start, int end) {
while (start <= end) {
if (arr[start] == target)
return start;
start++;
}
return -1; // 查找失败
}
假设原序列有序(递增),先查找序列中间的元素,如果相等返回坐标,如果小于目标则继续在后一半中进行二分查找,大于时在前一半进行二分查找.
时间复杂度O(logn)
int binSearch(int *arr, int target, int start, int end) {
if (start > end)
return -1;
int mid = (start + end) / 2;
if (arr[(start + end) / 2] == target)
return (start + end) / 2;
if (arr[(start + end) / 2] < target)
return binSearch(arr, target, (start + end) / 2 + 1, end);
return binSearch(arr, target, start, (start + end) / 2 - 1);
}