希尔,归并,快排,KMP
本文章是自己查询资料总结复习使用的,不喜勿喷
一、希尔排序是什么?
- 希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。
int shell_sort(int *data, int length) {
int gap = 0; //步长 分组的跨度
int i = 0, j = 0;
int temp;
for (gap = length / 2;gap >= 1; gap /= 2) { //把数组分成不同步长的两个为一组的数比较大小,小的放前面,大的放后面
for (i = gap;i < length;i ++) { //i是大的数组下标 每组遍历
temp = data[i];
for (j = i-gap;j >= 0 && temp < data[j];j = j - gap) { //如果前面的数大于后面的数则调换位置
data[j+gap] = data[j];
}
data[j+gap] = temp;
}
}
return 0;
}
希尔排序是非稳定排序算法。
- 希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组 进行插入排序操作,直至增量为1
- 我们分割待排序记录的目的是减少待排序记录的个数,并使整个序列向基本有序发展。而如上面这样分完组后,就各自排序的方法达不到我 们的要求。因此,我们需要采取跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
希尔排序是在直接插入排序和折半插入排序的前提下思考的,提高效率的办法
- 直接插入排序在什么情况下效率比较高
直接插入排序在基本有序时,效率较高
在待排序的记录个数较少时,效率较高- 希排序算法的特点:
缩小增量
多遍插入排序
1.时间复杂度:
O(n1.25)~ O(1.6n1.25)——经验公式
- 最好情况:由于希尔排序的好坏和步长gap的选择有很多关系,因此,目前还没有得出最好的步长如何选择(现在有些比较好的选择了,但不确定是否是最好的)。所以,不知道最好的情况下的算法时间复杂度。
- 最坏情况下:O(N*logN),最坏的情况下和平均情况下差不多。
已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,…)。
增量公式𝐷k=2k−1—相邻元素互质。 比如k从4开始,依次取15,7,3,1- 这项研究也表明“比较在希尔排序中是最主要的操作,而不是交换。”用这样步长序列的希尔排序比插入排序和堆排序都要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。
2.空间复杂度
- 由直接插入排序算法可知,我们在排序过程中,需要一个临时变量存储要插入的值,所以空间复杂度为1
3.算法稳定性
- 希尔排序中相等数据可能会交换位置,所以希尔排序是不稳定的算法。
注:
- 最后一个增量值必须为1,其它增量值无除了1之外的公因子
- 不宜在链式存储结勾上实现(因为链式存储结构找同一个增量的分组序列很麻烦)
归并排序
什么是归并排序?
归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法。
核心思想
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问 题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
- 稳定性
归并排序是一种稳定的排序。- 存储结构要求
可用顺序存储结构。也易于在链表上实现。- 时间复杂度
对长度为n的文件,需进行趟二路归并,每趟归并的时间为O(n),故其时间复杂度无论是在最好情况下还是在最坏情况下均是O(nlgn)。- 空间复杂度
需要一个辅助向量来暂存两有序子文件归并的结果,故其辅助空间复杂度为O(n),显然它不是就地排序。- 注意:若用单链表做存储结构,很容易给出就地的归并排序
void merge(int *data, int *temp, int start, int middle, int end) {
int i = start, j = middle+1, k = start;
//把较小的数先移到新数组中
while (i <= middle && j <= end) {
if (data[i] > data[j]) {
temp[k++] = data[j++];
//说明:temp[k++] = temp[k] ,只是在temp[k++]之后k的值加1
//说明:temp[++k] = temp[k+1],temp[++k]之后k的值加1
} else {
temp[k++] = data[i++];
}
}
while (i <= middle) { // 把左边剩余的数移入数组
temp[k++] = data[i++];
}
while (j <= end) { // 把右边边剩余的数移入数组
temp[k++] = data[j++];
}
for (i = start;i <= end;i ++) { //将temp中的内容全部拷贝到原数组中
data[i] = temp[i];
}
}
//1 归并排序(将两个集合合并到一起排序) 分而治之
int merge_sort(int *data, int *temp, int start, int end) {
int middle;
if (start < end) {
middle = start + (end - start) / 2;
//分而
merge_sort(data, temp, start, middle);
merge_sort(data, temp, middle+1, end);
//左右归并
merge(data, temp, start, middle, end); //治之
}
}
KMP算法
作用
- 是在一个已知字符串中查找子串的位置,也叫做串的模式匹配。
KMP算法的时间复杂度
可以实现复杂度为O(m+n)
为何简化了时间复杂度:
- 充分利用了目标字符串ptr的性质(比如里面部分字符串的重复性,即使不存在重复字段,在比较时,实现最大的移动量)。
//处理子串判断它是否相等,然后相等的话把下标插入到next列表中,不等的话插入0
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],注意next[k]是小于k的,无论k取任何值。
k = next[k-1];//往前回溯
if (pattern[q] == pattern[k]) { //如果相同,k++
k ++;
}
next[q] = k; //这个是把算的k的值(就是相同的最大前缀和最大后缀长)赋给next[q]
}
// next[0] = 0;
// q=1, k=0, pattern[q]:pattern[k] = b:a, next[1] = 0;
// q=2, k=0, pattern[q]:pattern[k] = c:a, next[2] = 0;
// q=3, k=0, pattern[q]:pattern[k] = a:a, k++, next[3] = 1;
// q=4, k=1, pattern[q]:pattern[k] = b:b, k++, next[4] = 2;
// q=5, k=2, pattern[q]:pattern[k] = c:c, k++, next[5] = 3;
// q=6, k=3, pattern[q]:pattern[k] = d:a, k=next[k-1] -> k=0; next[6] = 0;
}
int kmp(const char *text, const char *pattern, int *next) {
int n = strlen(text);
int m = strlen(pattern);
make_next(pattern, next); //计算next数组
int i, q;
for (i = 0, q = 0;i < n;i ++) { //ptr和str不匹配,且q>0(表示pattern和text有部分匹配)
while (q > 0 && pattern[q] != text[i]) { //匹配过的直接跳转到那个位置,不再进行重复校验匹配
q = next[q-1];//往前回溯
}
if (pattern[q] == text[i]) {
q ++;
}
if (q == m) {
//printf("Pattern occurs with shift: %d\n", (i-m+1));
break;
}
}
return i-q+1; //返回相应的位置
}