1.重要的排序算法
总共11种排序
但以下的排序用的较多,需要掌握,手撕代码
希尔排序
一种充分利用插入排序的特点的排序
1.元素少,排序效率高
2.数组接近有序效率高
流程
1.先取增量(增量递减)
2.根据增量分组
样本N个进行,根据gap分成多组 一层for循环
a[0],a[0+gap],a[0+2gap] +…
a[1],a[1+gap],a[1+2gap] +…
a[2],a[2+gap]
3.对每组进行,组内使用插入排序(两层for)
4.重复上述步骤 直到gap=1 (gap=1其实就是完全的插入排序)
详细见下图
一般gap首先取 长度/2 12/2=6
实现代码
void shellSort(int *data, int length)
{
// 分组每次取一半,最后到两重复上述步骤 直到gap=1 (gap=1其实就是完全的插入排序)
//3 ... 1 ... 4一定是 3 1和先比较 后面 3 4 再比较
for (int gap = length / 2; gap >= 1; gap /= 2)
{
for (int i = gap; i < length; i++)
{
int temp = data[i]; //保存当前需要调整的值
int j = i - gap; //取组内的前一个元素
// 每组遍历
while (j >= 0 && temp < data[j])
{
//组内排序 类似插入一样,前面有序整体后移
data[j + gap] = data[j];
j = j - gap;
}
//将temp插入合适位置
data[j + gap] = temp;
}
}
}
快速排序
快速排序算法首先会在序列中随机选择一个基准值,然后将除了基准值以外的数分
为“比基准值小的数”和“比基准值大的数”这两个类别,再将其排列成以下形式。
[ 比基准值小的数] 基准值 [ 比基准值大的数]
接着,对两个“[ ]”中的数据进行排序之后,整体的排序便完成了。
对“[ ]”里面的数据 进行排序时同样也会使用快速排序
流程
1.设置基准值(第一个值开始)设置i和j
2.从后面第一个值j开始找,找到第一个比data[i]小的数值 16,进行调换(可以先把哨兵保存temp中)
3.再从前面开始找 data[i++],找比data[j]=23数大的第一个数进行交换 64
4.重复23步骤 直到 i==j,哨兵归位
5.对两个集合进行递归执行 重复上述步骤
实现代码
int sort(int *data, int left, int right) {
//每一次递归, 每调用一次, 确定一个值得正确位置
if (left >= right)
return 0;
int i = left;
int j = right;
int key = data[left];
while (i < j) {
//从右边找到第一个小于哨兵的值
while (i < j && key < data[j]) { //
j --;
}
data[i] = data[j];
while (i < j && key >= data[i]) {
i ++;
}
data[j] = data[i];
}
// i == j
data[i] = key;
//
sort(data, left, i-1);
sort(data, i+1, right);
}
void quicksort2(int a[], int left, int right)
{
int i, j, t, temp;
i = left;
j = right;
temp = a[(i + j) / 2];//基数取中
while (i <= j)
{
while (a[j] > temp)//找到比基数小时j停止
j--;
while (a[i] < temp)//找到比基数大时i停止
i++;
if (i <= j)//满足条件交换
{
t = a[i];
a[i] = a[j];
a[j] = t;
i++;
j--;
}
}
if (left<j)//j在left右边,需要处理基数左边的序列
quicksort2(a, left, j);
if (i<right)//i在right左边,需要处理基数右边的序列
quicksort2(a, i, right);
}
int main() {
int i = 0;
int data[DATA_ARRAY_LENGTH] = {23, 64, 24, 12, 9, 16, 53, 57, 71, 79, 87, 97};
#if 0
shell_sort(data, DATA_ARRAY_LENGTH);
#else
quick_sort(data, DATA_ARRAY_LENGTH);
#endif
for (i = 0;i < DATA_ARRAY_LENGTH;i ++) {
printf("%4d", data[i]);
}
printf("\n");
}
归并排序
是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)
实现代码
void mergeSort(int arr[], int left, int right, int temp[])
{
if (left<right)
{
int mid = (left + right) / 2;
mergeSort(arr, left, mid, temp);//左边归并排序,使得左子序列有序
mergeSort(arr, mid + 1, right, temp);//右边归并排序,使得右子序列有序
merge(arr, left, mid, right, temp);//将两个有序子数组合并操作
}
}
void merge(int arr[], int left, int mid, int right, int temp[])
{
int i = left;//左序列指针
int j = mid + 1;//右序列指针
int t = 0;//临时数组指针
while (i <= mid && j <= right)
{
if (arr[i] <= arr[j])
temp[t++] = arr[i++];
else
temp[t++] = arr[j++];
}
while (i <= mid) {//将左边剩余元素填充进temp中
temp[t++] = arr[i++];
}
while (j <= right) {//将右序列剩余元素填充进temp中
temp[t++] = arr[j++];
}
t = 0;
//将temp中的元素全部拷贝到原数组中
while (left <= right)
{
arr[left++] = temp[t++];
}
}
冒泡排序
void bubbleSort(int b[],int len){
for (int i = 0; i<len-1; i++){
bool ok = true;
for (int j = 0; j <len-i-1; j++)
{
if (b[j]>b[j+1])
{
ok = false;
int tmp = b[j];
b[j] = b[j+1];
b[j] =tmp;
}
}
if(ok)
break;
}
}
KMP排序
1.基本概念
前缀:不包含最后一个字符
后缀:不包含第一个字符
最长公共前后缀:前缀和后缀共同的串
串 | 前缀 | 后缀 | 最长公共前后缀 | 长度 |
---|---|---|---|---|
a | x | x | x | 0 |
ab | a | b | x | 0 |
abc | a,ab | c,bc | x | 0 |
abca | a,ab,abc | a,ca,bca | a | 1 |
abcab | a,ab,abc | b,ab,cab,bcab | ab | 2 |
abcabc | a,ab,abc,abca,abcab | b,bc,abc,cabc,bcabc | abc | 3 |
next数组
通过公共前后缀长度求出next数组,用于匹配失败时进行回溯
实现代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//
// shell , quick, kmp
// pattern
//
// abcabc
// k = 0 前缀
// q = 1 后缀
void make_next(const char *pattern, int *next) {
int q, k;
int m = strlen(pattern);
next[0] = 0; //
for (q = 1,k = 0;q < m; q ++) {
while (k > 0 && pattern[q] != pattern[k]) {
k = next[k-1];//难点 这里回退
//整个串里面有a b c三个子串a与b也有一段匹配求了存在在next中 b和c匹配一段
//b和c匹配直到某个字符部匹配则回溯到a与b匹配则 a也与c前一段匹配
}
if (pattern[q] == pattern[k]) { // 如果前缀与后缀有相同的字符
k ++;
}
next[q] = k;
}
}
int kmp(const char *text, const char *pattern, int *next) {
int n = strlen(text);
int m = strlen(pattern);
make_next(pattern, next);
int i, q;
for (i = 0, q = 0;i < n;i ++) { //i --> text, q --> pattern
#if 1
while (q > 0 && pattern[q] != text[i]) {
q = next[q-1];//q下标回溯 i下标不用回溯 优于暴力匹配
}
#endif
if (pattern[q] == text[i]) {
q ++;
}
// q == m ---> 匹配成功
if (q == m) {
return i-q+1;
}
}
return -1;
}
int main() {
char *text = "abcabcabcabcabcd";
char *pattern = "abcabcd";
int next[20] = {0};
int idx = kmp(text, pattern, next);
printf("match pattern: %d\n", idx);
}
堆排序
//堆排序
class HeapSortSolution
{
//对有一定顺序的堆,当前第i个结点取根左右的最大值(这个操作称heapfiy)
void heapify(vector<int> &nums, int n, int i)
{
int l = i * 2 + 1, r = i * 2 + 2;
int max = i;
if (l < n && nums[l] > nums[max])
max = l;
if (r < n && nums[r] > nums[max])
max = r;
if (max != i)
{
swap(nums[max], nums[i]);
//因为交换了,还需要向下调整
heapify(nums, n, max);
}
}
//建立大根堆
void heapify_build(vector<int> &nums, int n)
{
//由最后一个结点下标是n-1,parent = (i-1)/2
//从树的倒数第二层第一个结点开始,对每个结点进行heapify操作,然后向上走
int temp = (n - 2) / 2;
for (int i = temp; i >= 0; i--)
heapify(nums, n, i);
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << " ";
cout << endl;
}
//数组 总共有n个结点
void heapify_sort(vector<int> &nums, int n)
{
//建立大根堆
heapify_build(nums, n);
//每次交换最后一个结点和根节点(最大值),
//对交换后的根节点继续进行heapify(此时堆的最后一位是最大值,因此不用管他,n变为n-1)
for (int i = 0; i < n; i++)
{
swap(nums.front(), nums[n - i - 1]);
heapify(nums, n - i - 1, 0);
}
}
};