数据结构与算法------常见的排序与KMP

本文介绍了常见的排序与KMP算法。排序方面,讲解了插入排序和希尔排序,希尔排序是插入排序的改进版。KMP算法部分,说明了获取next数组的方法,还详细阐述了求解KMP算法的过程,通过具体例子展示如何利用部分匹配表提高搜索效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.常见的排序与KMP

  1. 插入排序:通过构造有序序列,对于未排序的数据,在已排序序列中从后向前扫描,找到相应位置并插入。
int insertionSort(int *array,int length) 
{
    int i;
    for (i = 1; i < length; i++) {
        int current = array[i];
        int preIndex = i-1;
        while (preIndex >=0 && array[preIndex] > current) {
            /* code */
            array[preIndex+1] = array[preIndex];
            preIndex--;
        }
        array[preIndex+1] = current;
    }
    return 1;
}
  1. 希尔排序:第一个突破O(N2)的排序算法,是插入排序的改进版。
    算法描述:
    先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

/*
**希尔排序
*/
int shellSort(int *array, int length)
{
    int gap = 0; //分组的跨度
    int i = 0;
    for (gap = length / 2; gap >= 1; gap /= 2) {
        for (i = gap; i < length; i++) {
            int current = array[i];
            int preIndex;
            for (preIndex = i - gap; preIndex >= 0 && array[preIndex] > current; preIndex = preIndex -gap) {
                array[preIndex + gap] = array[preIndex];
            }
            array[preIndex + gap] = current;
        }
    }
    return 1;
}
  1. KMP算法
    5.1获取next数组
    获取next数组可以参考https://www.zhihu.com/question/21923021链接,如下所示
    如何编程快速求得next数组。其实,求next数组的过程完全可以看成字符串匹配的过程,即以模式字符串为主字符串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。具体来说,就是从模式字符串的第一位(注意,不包括第0位)开始对自身进行匹配运算。 在任一位置,能匹配的最长长度就是当前位置的next值。如下图所示。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    5.2 求解kmp算法
    参考链接:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

1.首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。
在这里插入图片描述
2.因为B与A不匹配,搜索词再往后移。
在这里插入图片描述
3.就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。
在这里插入图片描述
4.接着比较字符串和搜索词的下一个字符,还是相同。
在这里插入图片描述
5.直到字符串有一个字符,与搜索词对应的字符不相同为止。
在这里插入图片描述
6.这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。
在这里插入图片描述
7.一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。
在这里插入图片描述
8.怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。next组由上面已有讲解
在这里插入图片描述
9.已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:移动位数 = 已匹配的字符数 - 对应的部分匹配值
在这里插入图片描述
10.因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2(“AB”),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。
在这里插入图片描述
11.因为空格与A不匹配,继续后移一位。
在这里插入图片描述
12.逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。
在这里插入图片描述
13.逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

typedef struct Node {
    int value;
    struct Node *next;
} ListNode;

//打印信息
void showList(ListNode *head) {
    ListNode *p = head;
    while (p->next!=NULL) {
        p = p->next;
        printf("%d ", p->value);
    }
    printf("\n");
}

//初始化链表
//isLink :true 创建有环链表
ListNode *initList (int n, bool isLink) {
    ListNode *head, *target, *temp, *link;
    target = (ListNode*)malloc(sizeof(ListNode));
    head = target;
    for (int i = 0; i < n; i++) {
        temp = (ListNode*)malloc(sizeof(ListNode));
        temp->value = 2 * i +1;
        target->next = temp;
        target = temp;
        if (isLink && i == n/2) {
            link = target;
        }
    }
    if (isLink) {
        target->next = link;
    }else {
        target->next = NULL;
    }
    return head;
}
//创造第二个相交链表
ListNode *createTwoIntersectList(ListNode *first, const int n) {
    ListNode *temp = first;
    ListNode *target, *temp2, *second;
    target = (ListNode *)malloc(sizeof(ListNode));
    second = target;
    for (int i=0; i<n/2; i++) {
        temp = temp->next;
    }
    for (int i = 0; i < n/2; i++) {
        temp2 = (ListNode *)malloc(sizeof(ListNode));
        temp2->value = 3 * i;
        target->next = temp2;
        target = temp2;
    }
    target->next = temp;
    return second;
}

/*在单向链表中快速找到倒数第n个节点*/
ListNode *getLastNnode(ListNode *head, int n) {
    ListNode *first = head;
    ListNode *second = NULL;
    while(n-- > 0 && first != NULL) {
        first = first->next;
    }
    if (first != NULL) {
        second = head;
    }
    while(first != NULL) {
        first= first->next;
        second = second->next;
    }
    return second;

}

/*
判断链表是否有环
思路:快慢指针
*/
bool judgeListRing(ListNode *head) {
    ListNode *slow = head;
    ListNode *fast = head;
    while(slow != NULL && fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) {
            return true;
        }
    }
    return false;
}

/*判断两个链表是否交叉*/
ListNode *intersectList(ListNode *head1, ListNode *head2) {
    if (head1 == NULL || head2 == NULL) {
        printf("head1 or head2 is null\n");
        return NULL;
    }
    ListNode *first = head1, *second = head2;
    int head1Num = 0, head2Num = 0;
    while(first->next != NULL ) {
        head1Num++;
        first = first->next;
    }
    while(second->next != NULL) {
        head2Num++;
        second = second->next;
    }
    if (first != second) {
        return NULL;
    }
    int differNum = (head1Num > head2Num) ?  (head1Num - head2Num) : (head2Num - head1Num) ;
    if (head1Num > head2Num) {
        first = head1;
        second = head2;
    }else {
        first = head2;
        second = head1;
    }
    while(differNum--) {
        first = first->next;
    }
    while(first != second) {
        first = first->next;
        second = second->next;
    }
    return first;
}
/*链表反转*/

ListNode *reverseNodeList(ListNode *head) {
    ListNode *prev = NULL;
    ListNode *current = head;
    while(current) {
        printf("nNode1:%d\n", current->value);
        ListNode *node = current->next;
        current->next = prev;
        prev = current;
        current = node;
    }
    return prev;
}
//2.头节点插入法
int main (){
    ListNode *node = initList(10, false);
    //打印链表
    showList(node);
    //找到倒数第n个链表
    ListNode *nNode = getLastNnode(node, 3);
    printf("nNode:%d\n", nNode->value);
    //判断链表是否有环
    ListNode *linkNode = initList(10, true);
    bool result = judgeListRing(linkNode);
    printf("是否有环:%d\n", result);
    //判断两个链表是否相交
    ListNode *first = initList(10, false);
    ListNode *second = createTwoIntersectList(first, 10);
    showList(first);
    showList(second);
    ListNode *intersect = intersectList(first, second);
    printf("相交链表:%d\n",intersect->value);
    //链表反转
    ListNode *rev = initList(8, false);
    showList(rev);
    ListNode *reverseNode = reverseNodeList(rev);
    showList(reverseNode);
    return 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值