科林算法_1 线性结构

目录

一、算法基础

1.1 时空效率

1.2 数据结构

1.2.1 数组

1.2.2 位运算

1.3 递归

1.4 分治法

1.5 二分

二、链表

2.1 链表结构

数组和链表的区别

2.2 链表应用

三、哈希表

map和unorder_map原理

vecter和list区别

四、栈 FILO

五、队列 FIFO

六、字符串(末尾有'\0'的字符数组)

6.1 查找问题

6.1.1 在一个串中找符合条件的某个字符

6.1.2 在一个串中找符合条件的子串

6.1.2.1 KMP

6.1.2.2 Sunday

6.1.3 在多个串中找复合条件的某个串

6.1.4 找两个串的公共部分


一、算法基础

1.1 时空效率

时间复杂度:代码的总的运行次数->对内存访问的次数.

渐进符号 O(大欧):上界,等价于 <=

空间复杂度:空间消耗(为了解决问题额外的空间消耗)

常量简化为O(1),程序在有限可数的范围内完成。空间时间消耗不随待处理量的增大而增大

线性变化O(n)

复杂度要求:

1.多项式级运算结果,只保留最大项

2.常系数要舍去

3.如果程序可以在有限可数的资源消耗内完成,即为常量复杂度O(1)

经验性结论:

1.一个简单的顺序或选择结构,时间复杂度是O(1)

2.一般的一层循环时间复杂度是O(n)

3.一般的两层循环复杂度是O(n2)

4.顺序的两个循环,时间复杂度是max(O(n),O(m))

5.一般情况下的二分分治时间与log2n有关

6.时空权衡问题,大多数情况下,选择用空间换时间。可以用递归、分治、动态规划来换取时间效率

1.2 数据结构

数据结构:

数据排列和组合的形态
用来进行数据的存储和运算
线性结构(一对一)

非线性结构(一对多)

1.2.1 数组

数组定义:在内存中连续存储固定大小的具有相同类型元素的顺序集合,可以用下标表示每个元素

数组特性:

1、类型相同、空间连续、长度固定

2、下标查找快,遍历慢

3、尾增删易,插入删除难

例:数组查重

//数组查重:n个元素,0~n-1
#include <iostream>
using namespace std;

bool check(int* a, int len) {
	for (int i = 0; i < len; i++) {
		if (i != a[i]) {
			if (a[a[i]] == a[i]) {
				return 0;
			}
			else {
				swap(a[i], a[a[i]]);
				i--;
			}
		}
	}
	return 1;
}

int main() {
	int a[] = { 1,2,1,3,4 };
	cout << check(a, sizeof(a));
}

1.2.2 位运算

136. 只出现一次的数字 - 力扣(LeetCode)

//n个元素,有一个元素仅出现一次,其他元素均出现两次,怎么找到这个元素
#include <iostream>

using namespace std;

int findans(int* a, int n) {
	int res = 0;
	for (int i = 0; i < n; i++) {
		res = res ^ a[i];
	}
	return res;
}

int main() {
	int a[] = { 1,2,2,3,1,5,6,9,6,3,9 };
	int len = sizeof(a) / sizeof(a[0]);
	cout << findans(a,len);
}

 260. 只出现一次的数字 III - 力扣(LeetCode)

//n个元素,有2个元素仅出现一次,其他元素均出现两次,怎么找到这个元素
#include <iostream>

using namespace std;

void findans(int* a, int n,int& x,int& y) {
	//整体^,得到结果两值的异或值
	int res = 0;
	for (int i = 0; i < n; i++) {
		res = res ^ a[i];
	}
	//找到结果最右非0位,说明结果的两个值,在这一位不同
	//res = ~res + 1;	//ERROR:溢出
	//res=res&(-res);		//ERROR:2147483648 的否定不能用类型“int”表示;当值位INT_MIN的时候,需要特殊判断
	res = (res == INT_MIN) ? res : res & -res;    //[1,1,0,-2147483648]
	//根据这不同的一位,对原数据分组
	for (int i = 0; i < n; i++) {
		//每组^,得到结果的两个数
		if (res & a[i])
			x = x ^ a[i];
		else y = y ^ a[i];
	}
}

int main() {
	int a[] = { 0,0,1,2,2,3,3,4,4,5,6,6,7,7 };
	int len = sizeof(a) / sizeof(a[0]);
	int x = 0, y = 0;
	findans(a, len, x, y);
	cout << x << " " << y << endl;
}

 137. 只出现一次的数字 II - 力扣(LeetCode)

//n个元素,有1个元素仅出现一次,其他元素均出现3次,怎么找到这个元素
//按位统计1个数count,count一定是3的倍数+1或者+0,那么结果的这位就是count%3
#include <iostream>

using namespace std;

int findans(int* a, int n) {
	int res = 0;
	for (int i = 0; i < 32; i++) {
		int count = 0;
		for (int j = 0; j < n; j++) {
			if ((a[j] >> i) & 1) {
				count++;
			}
		}
		res = res | ((count % 3) << i);
	}
	return res;
}

int main() {
	int a[] = { 1,1,1,3,4,4,4,5,5,5,6,6,6 };
	int len = sizeof(a) / sizeof(a[0]);
	cout << findans(a, len);
}

使用位运算,避免越界问题,同时位运算快。

交换操作:

注意:ab不能代表同一块空间,且不能值相同

(a^b)&&(a=a^b);

x^=x;        //清0操作

a^b         //判断是否相等

a+b  ——> (a^b)+((a&b)<<1) ——> (a^b)^((a&b)<<1)        //加数 + 进位,直到没有进位

1611. 使整数变为 0 的最少操作次数 - 力扣(LeetCode)

1.3 递归

return回到程序的调用位置 exit结束当前进程

斐波那契数列

将大的问题拆解成解决方案完全相同的子问题。要有明确的终止

1.4 分治法

将一个问题拆解成若干个解决方法完全相同的子问题

1.问题难度随问题缩小而降低

2.问题可拆分

3.子问题的解可合并

4.子问题的解相互独立

 例:快速幂

50. Pow(x, n) - 力扣(LeetCode)

1.5 二分

二分查找/折半搜索

等概率事件,可以得到最优下界(次数内一定能解决问题)

条件:有序的

//二分查找

#include <iostream>

using namespace std;

int ef1(int* a,int left,int right,int ans) {
	//int mid = (left + right) / 2;	//容易溢出
	int mid = left + (right - left) / 2;
	if (mid == left && a[mid] != ans)
		return -1;
		if (a[mid] == ans)
			return mid;
		else if (a[mid] < ans)
			ef1(a, mid+1, right, ans);
		else ef1(a, left, mid - 1, ans);
	
}

int ef2(int* a, int n,int ans) {
	int left = 0;
	int right = n;
	int mid=-1;
	while (left < right) {
		mid = left + (right - left) / 2;
		if (mid == left && a[mid] != ans) {
			mid = -1;
			break;
		}
		if (a[mid] == ans)
			break;
		else if (a[mid] < ans) {
			left = mid + 1;
		}
		else {
			right = mid;
		}
	}
	return mid;
}

int main() {
	int a[] = { 1,2,3,4,4,5,6,7,10,23,25,35,65,72,89,100 };
	int len = sizeof(a) / sizeof(a[0]);
	cout << ef1(a,0,len,35)<<endl;
	cout << ef2(a, len, 1111)<<endl;
}

二、链表

2.1 链表结构

链表的定义:一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

链表特性:中间插入和删除非常快

数组和链表的区别

(1)逻辑结构:数组内存中连续,长度大小固定。链表采用动态内存分配,内存中不连续,大小不固定。

(2)内存结构:数组从栈上分配内存,系统自动分配空间,自由度小。链表从堆上手动分配内存,自由度大。

堆区与栈区的区别?

一文读懂堆与栈的区别_堆栈-CSDN博客

1、生长方向:堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。

2、分配方式:堆是手工动态分配的malloc()。栈由操作系统静态分配(局部变量)或动态分配alloca()

malloc、alloc、calloc、realloc_malloc alloc calloc-CSDN博客

3、分配效率:栈由操作系统自动分配,会在硬件层级对栈提供支持,效率较高。堆由C/C++提供的库函数或运算符完成,易产生内存碎片,效率低下。

4、存储内容:堆中具体内容由程序员来填充。栈存放函数返回地址、相关参数、局部变量和寄存器内容等

5、管理方式:栈由系统自动分配释放。堆的申请和释放都由手工控制,易产生内存泄漏

6、空间大小:每个进程拥有的栈大小远小于堆

(3)访问效率:数组可通过下标访问,查找效率高,插入删除难。链表需从头遍历,插入删除效率高。

链表:数据域、指针域

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

typedef struct node {
	int data;
	node* next;
}List;

void CreateList(List** p) {
	int len;
	//List* pHead = nullptr;
	List * pTail = nullptr;
	List* pNode = nullptr;
	printf("请输入链表长度\n");
	scanf("%d", &len);
	while (len--) {
		printf("输入节点数值\n");
		int val;
		scanf("%d", &val);

		pNode = (List*)malloc(sizeof(List));
		pNode->data = val;
		pNode->next = nullptr;

		if (*p == nullptr) {
			*p = pNode;
		}
		else {
			pTail->next = pNode;
		}
		pTail = pNode;
	}
}

void printList(List* pHead) {
	while (pHead != nullptr) {
		printf("%d ", pHead->data);
		pHead = pHead->next;
	}
	printf("\n");
}

void reversPrint(List* p) {
	if (p == nullptr) {
		return;
	}
	reversPrint(p->next);
	printf("%d ", p->data);
}

int main() {
	List* p = nullptr;
	CreateList(&p);
	printList(p);
	reversPrint(p);
	printf("\n");
}

2.2 链表应用

循环链表:从任意节点出发,均可遍历整个链表

线性链表:

  • 线性表都只有一个表头元素和一个表尾元素;
  • 表头没有前驱,表尾没有后继;
  • 除表头和表尾外,其他元素只有一个直接前驱和一个直接后继

数组模拟链表:除了存储数据,还有一个区域,存储下一个节点的下标

例:双指针

143. 重排链表 - 力扣(LeetCode)

例:两链表交点?

LCR 023. 相交链表 - 力扣(LeetCode)

1、哈希

2、 双指针,从a、b链表头同时移动,跑完a再从b头跑,返回指向同一个的结点(a+c+b=b+c+a)

3、记录两条链表长度,对齐,同时移动,返回指向同一个的结点

例:有环链表求入口节点?

LCR 022. 环形链表 II - 力扣(LeetCode)

1、哈希

2、快慢指针

a=(n-1)(b+c)+c,发现:从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。

3、先求得环长,ab指针从头,b先跑环长,ab同时跑,b到头,a到入口节点

4、反转链表,会回到表头。再反转还原链表,记录处理过程,可以得到入口节点

abcgfedcba

复杂链表,含有随机指针

LCR 154. 复杂链表的复制 - 力扣(LeetCode)

跳跃链表(skipList跳表)(复合结构)

对有序链表进行快搜

哨兵

Skip List--跳表(全网最详细的跳表文章没有之一) - 简书 (jianshu.com)

例:无序数组中查找合为n的两个数?

1. 两数之和 - 力扣(LeetCode)

三、哈希表

应用:无序数组的多次查找

步骤:

(一)定散列(Hash)函数,按一定规则进行映射。常用除留取余法,p=key%M,M常取>=n的质数(减少冲突的可能性)

(二)解决哈希冲突。多个数据抢占同一个位置时,叫做哈希冲突。

(1) 开放地址法:① 线性探测:依次占用后续没有占用的位置

                           ② 线性补偿探测:定义一个间隔,按间隔快速的向后查找后续没有占用的位置

                           ③ 二次探测、线性探测散列:查找前后位置x^2(x=1,2,3,4,5...)的位置是否被占用

(2) 拉链法、链地址法:将哈希数组(指针数组)变成多条单向链表(省空间),发生冲突的键值对添加到对应的链表中

1. List struct

2. 表头数组hashTable,赋初值为空

3. 元素按哈希入组(头插,简单)

4. 查找,获得索引、遍历

两种方法对比:

①线性探测:本质是数组

装载因子α=元素个数/表长 < 0.8(元素少,表长)越稀疏,产生冲突的可能性越低

旧表转移到新表,散列函数也会有变化

线性探测删除:标记为删除态

②拉链法:兼具链表和数组

α < 1 ,链表过长会导致遍历时间长

扩容,节点可以直接指向新表,省去了空间开销

#include <iostream>

using namespace std;

#define M (13)

typedef struct node{
	int vlue;
	node* next;
}List;

List* hashTable[M] = { nullptr };

void insert(int v) {
	List* nd = (List*)malloc(sizeof(node));
	if (nd !=nullptr)
		nd->vlue = v;
	int hashvalue = v % M;
	nd->next = hashTable[hashvalue];
	hashTable[hashvalue] = nd;
}

bool Search(int v) {
	int hashvalue = v % M;
	cout << hashvalue << endl;
	if (hashTable[hashvalue] == nullptr)
		return 0;
	List* head = hashTable[hashvalue];
	while (head) {
		if (head->vlue == v)
			return 1;
		head = head->next;
	}
	return 0;
}

int main() {
	int a[] = { 85,14,2,116,34,7,82,15,26,45,202,31 };
	int len = sizeof(a) / sizeof(a[0]);
	for (int i = 0; i < len; i++) {
		insert(a[i]);
	}
	for (int i = 0; i < M; i++) {
		cout << i << ":";
		List* p = hashTable[i];
		while (p) {
			cout << p->vlue << " ";
			p = p->next;
		}
		cout << endl;
	}
	cout << Search(5);
}

拉链法优点:1.处理冲突简单 2.删除简单 3.对于未知数据个数,拉链法可以用通过扩容来解决 4.数据量大,数据体积大(适合拉链法)。数据量小的时候更适合线性探测。

map和unorder_map原理

unordered_map和map的区别,从算法,底层实现原理区别,效率,桶树等结构等等多个层面解析(c++角度)-CSDN博客

vecter和list区别

vector和list的区别+应用(非常详细的答案)_vector和list的区别,应用,越详细越好-CSDN博客

四、栈 FILO

pop销毁栈顶,让当前变量用不了。此时top就不能定义在主函数(生存周期),定义另外一个结构体对top进行封装。

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

typedef struct node {
	int value;
	struct node* pnext;
}Data;

typedef struct node2 {
	Data* pTop;
	int count;
}Stack;

void Init(Stack** pStack) {
	*pStack = (Stack*)malloc(sizeof(Stack));
	(*pStack)->pTop = NULL;
	(*pStack)->count = 0;
}

void Push(Stack* pStack, int num) {
	if (pStack == NULL) {
		printf("stack is not exist\n");
		return;
	}

	Data* pTmp = (Data*)malloc(sizeof(Data));
	pTmp->value = num;
	pTmp->pnext = pStack->pTop;
	pStack->pTop = pTmp;
	pStack->count++;
}

void Pop(Stack* pStack) {
	if (pStack == NULL)
		return;
	if (pStack->count == 0)
		return;
	Data* pDel = pStack->pTop;
	pStack->pTop = pStack->pTop->pnext;
	free(pDel);
	pStack->count--;
}

void Clear(Stack* pStack) {
	if (pStack == NULL)
		return;
	while (pStack->count > 0) {
		Pop(pStack);
	}
}

void Destroy(Stack** pStack) {
	Clear(*pStack);
	free(*pStack);
	*pStack = NULL;
}

int GetTop(Stack* pStack) {
	if (pStack == NULL)
		exit(1);
	if (pStack->count == 0)
		exit(1);
	return pStack->pTop->value;
}

int GetCount(Stack* pStack) {
	if (pStack == NULL)
		exit(1);
	return pStack->count;
}

int IsEmpty(Stack* pStack) {
	if (pStack == NULL)
		exit(1);
	return pStack->count == 0 ? 1 : 0;
}

int main() {
	Stack* pStack = NULL;
	Init(&pStack);
	Push(pStack, 1);
	Push(pStack, 2);
	Push(pStack, 3);
	Push(pStack, 4);

	printf("%d ", GetTop(pStack));
	Pop(pStack);
	printf("%d ", GetTop(pStack));
	Pop(pStack);
	printf("%d ", GetTop(pStack));
	Pop(pStack);
	printf("%d ", IsEmpty(pStack));
	Destroy(&pStack);
	Push(pStack, 100);
	return 0;
}

栈的应用

栈在四则运算中的应用:逆波兰表达式

中缀表达式转后缀表达式

‘( ’ => 无条件入栈

数字或字母 => 输出

运算符 => 把当前符号与栈顶元素进行优先级比较。当前符号优先级高,直接入栈;优先级低,栈内元素出栈,直到比当前符号优先级低,再将当前符号入栈。

')' => 栈内元素出栈,直到 '(' 停止。(前缀中缀表达式有括号,后缀表达式没有括号)

(9+6)*5-8/4

9 6 + 5 * 8 4 / -

简单方法:给所有可能的运算加括号

(((9+6)*5)-(8/4))

把符号转到所在的括号后

(((9 6)+ 5)* (8 4)/)-

然后再删去括号

后缀转中缀

数字或字母 => 入栈

运算符 => 将栈顶元素和栈顶元素的下一个构成表达式(加括号)

单调栈

实时返回栈内的最小值

维护一个单调递减栈,每有元素入栈,在单调栈里入栈当前最小值。这样单调栈栈顶永远是当前最小值。

155. 最小栈 - 力扣(LeetCode)

五、队列 FIFO

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

typedef struct node {
	int vlaue;
	struct node* pnext;
}Data;

typedef struct node2 {
	int count;
	Data* pFront;
	Data* pRear;
}Queue;

void Init(Queue** pQueue) {
	*pQueue = (Queue*)malloc(sizeof(Queue));
	(*pQueue)->count = 0;
	(*pQueue)->pFront = NULL;
	(*pQueue)->pRear = NULL;
}

void Push(Queue* pQueue, int num) {
	if (pQueue == NULL)
		return;
	Data* pTmp = NULL;
	pTmp = (Data*)malloc(sizeof(Data));
	pTmp->vlaue = num;
	pTmp->pnext = NULL;

	if (pQueue->pFront == NULL)
		pQueue->pFront = pTmp;
	else
		pQueue->pRear->pnext = pTmp;
	pQueue->pRear = pTmp;
	pQueue->count++;

}

void Pop(Queue* pQueue) {
	if (pQueue == NULL)
		return;
	if (pQueue->count == 0)
		return;
	Data* pDel = pQueue->pFront;
	pQueue->pFront = pQueue->pFront->pnext;
	printf("%d\n", pDel->vlaue);
	free(pDel);
	pDel = NULL;
	pQueue->count--;
	if (pQueue->count == 0)
		pQueue->pRear = NULL;
}

int main() {
	Queue* pQueue = NULL;
	Init(&pQueue);
	Push(pQueue, 1);
	Push(pQueue, 2);
	Push(pQueue, 3);
	Push(pQueue, 4);
	Pop(pQueue);
	Pop(pQueue);
	Pop(pQueue);
	Pop(pQueue);
	return 0;
}

循环队列(底层是数组)

约瑟夫环

#include <iostream>
#include <queue>

using namespace std;

int Joseph(int n, int k) {
	queue<int> q;
	for (int i = 1; i <= n; i++) {
		q.push(i);
	}
	int num = 0;
	int count = 0;
	while (q.size() > 1) {
		num = q.front();
		q.pop();
		count++;
		if (count % k != 0) {
			q.push(num);
		}
	}
	num = q.front();
	q.pop();
	return num;
}

int main() {
	int n, k;
	cin >> n >> k;

	cout << Joseph(n, k);
}

用循环队列实现约瑟夫环(C语言实现)-CSDN博客

可得,第i个元素在一次出队操作时,位置向前移动了k。反过来,进行一次入队操作时,i元素向后移动了k。当队列只剩一个元素时,K(1)=0;进行一次入队时,K(2)=(K(1)+k)%2。

所以,K(n)=(K(n-1)+k)%n 

1823. 找出游戏的获胜者 - 力扣(LeetCode)

子数组、子串:连续

子序列:可以不连续,相对位置不变

子集:包含

找长度为k的连续子数组中的最大值

求数组中连续k个数最大值-CSDN博客

维护一个单调递减队列,当有值大于队尾元素时,popback直到队尾元素比新元素大(>=)或队列为空,然后pushback新元素。(滑动窗口)

239. 滑动窗口最大值 - 力扣(LeetCode)

两个栈,实现当前队列的先进先出

入队栈和出队栈。入队时,先把 出队栈 出栈到 入队栈 再入栈新元素;出队时,先把 入队栈 出栈到 出队栈,再出栈 出队栈。

#include <iostream>
#include <stack>

using namespace std;

int pop(stack<int>* s1, stack<int>* s2) {
	if (s1->empty() && s2->empty()) {
		cout << "empty" << endl;
		return 0;
	}
	while (!s1->empty()) {
		int tmp = s1->top();
		s1->pop();
		s2->push(tmp);
	}
	int tmp = s2->top();
	s2->pop();
	return tmp;
}

void push(stack<int>* s1, stack<int>* s2,int val) {

	while (!s2->empty()) {
		int tmp = s2->top();
		s2->pop();
		s1->push(tmp);
	}
	s1->push(val);
}

int main() {
	stack<int> s1;
	stack<int> s2;
	push(&s1, &s2, 1);
	push(&s1, &s2, 2);
	push(&s1, &s2, 3);
	push(&s1, &s2, 4);
	push(&s1, &s2, 5);
	cout << pop(&s1, &s2) << endl;
	cout << pop(&s1, &s2) << endl;
	cout << pop(&s1, &s2) << endl;
	push(&s1, &s2, 6);
	push(&s1, &s2, 7);
	cout << pop(&s1, &s2) << endl;
	cout << pop(&s1, &s2) << endl;
	cout << pop(&s1, &s2) << endl;
	cout << pop(&s1, &s2) << endl;
	cout << pop(&s1, &s2) << endl;
}

两个队列,实现栈的先进后出

出栈时,先把 非空队列 除尾元素出队到 空队列,非空队列剩余元素弹出。

入栈时,入队非空队列。

#include <queue>
#include <iostream>

using namespace std;

int pop(queue<int>& l1, queue<int>& l2) {
	if (l1.empty() && l2.empty()) {
		cout << "empty()" << endl;
		return 0;
	}
	queue<int>& ln = l1.empty() ? l2 : l1;
	queue<int>& le = l1.empty() ? l1 : l2;
	while (ln.size() > 1) {
		int tmp = ln.front();
		ln.pop();
		le.push(tmp);
	}
	int ans = ln.front();
	ln.pop();
	return ans;
}

void push(queue<int>& l1, queue<int>& l2, int val) {
	queue<int>& l = l1.empty() ? l2 : l1;
	l.push(val);
}

int main() {
	queue<int> l1;
	queue<int> l2;
	push(l1, l2, 1);
	push(l1, l2, 2); 
	push(l1, l2, 3);
	push(l1, l2, 4);
	push(l1, l2, 5);
	cout << pop(l1, l2) << endl;
	cout << pop(l1, l2) << endl;
	cout << pop(l1, l2) << endl;
	push(l1, l2, 6);
	cout << pop(l1, l2) << endl;
	cout << pop(l1, l2) << endl;
	cout << pop(l1, l2) << endl;
	cout << pop(l1, l2) << endl;
}

六、字符串(末尾有'\0'的字符数组)

字符串数组和字符串指针的区别

char str[] = "hello";

char *str = "hello";

1. 字符串数组存储位置在栈区,字符串指针在全局静态区

2. 大小不一样,字符串数组大小为6('\0')。指针大小为4

3. 权限不同,数组可以增删改查,指针只能查

足够长的字符串,对其所有的空格替换?

统计空格数,然后通过指针从尾部向前移动复制

反转字符串中的单词

151. 反转字符串中的单词 - 力扣(LeetCode)

C++中的reverse()函数_c++ reverse函数-CSDN博客

字符串函数

 strlen长度 strcmp比较 strcat连接 strstr查找位置 strcpy复制 substr子串 atoi字符串转数字(手写源码) sprintf拼接字符串 strtok 分解字符串为一组字符串 strdup字符串拷贝库

C 标准库 – <string.h> | 菜鸟教程 (runoob.com)

 正则表达式

. 匹配任意一个字符

* 匹配前一个字符0次或多次

判断正则表达式是否能匹配字串

状态机

6.1 查找问题

6.1.1 在一个串中找符合条件的某个字符

找第一个只出现一次的字符?

桶排,开个256大小的数组计数

6.1.2 在一个串中找符合条件的子串

6.1.2.1 KMP

KMP算法图文详解(为什么是next[0]=-1、next[j]=k和k=next[k])_kmp算法的next初始值为-1-CSDN博客

KMP 改进的字符串匹配算法。用来查找匹配串(match)在原串(src)中的位置。

不让主串指针移动,而是让匹配串向前移动 j - 当前 前缀后缀相同的最大匹配长度 (在匹配失败时,让匹配部分的前缀与相同的后缀对齐,省去重复检查这一段的时间。同时取最大长度,使match尽可能靠左的进行比较)

若移动时没有采取最大匹配长度,会导致匹配时错过答案

①求前缀后缀最大匹配长度next (用于下一次匹配的快速定位)

void getnext(string s, int next[],int len) {
	cout << __func__ << endl;
	int i = 1, j = 0;
	next[0] = 0;
	while (i < len) {
		if (s[i] == s[j]) {
			next[i] = j + 1;
			i++;
			j++;
		}
		else if (j == 0) {
			next[i] = 0;
			i++;
		}
		else j = next[j - 1];
	}
}

src:     abcabceabcabcdabcabcabcdabcabcfabcabc

match:abcabcdabcabcf

next:  00012301234560

②进行匹配。当原串和匹配串匹配,二者同时移动;若二者不匹配,匹配串指针移动到next[j-1]

int KMP(string src, string match) {
	cout << __func__ << endl;
	int len_s = src.size();
	int len_m = match.size();
	int* next = (int*)malloc(len_m * sizeof(int));
	getnext(match, next, len_m);
	int i = 0, j = 0;
	while (i < len_s && j < len_m) {
		if (src[i] == match[j]) {
			i++;
			j++;
		}
		else if (j == 0) {
			i++;
		}
		else j = next[j - 1];
	}
	if (j >= len_m)
		return i - len_m;
	else return -1;
}

③检测匹配串是否遍历完成

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

int* getnext(const char* s) {
	int i = 1, j = 0;
	int len = strlen(s);
	int* next = (int*)malloc(len * sizeof(int));
	next[0] = 0;
	while (i < len) {
		if (s[i] == s[next[j]]) {
			next[i] = next[j] + 1;
			i++;
			j=i-1;
		}
		else if (next[j] == 0) {
			next[i] = 0;
			i++;
			j = i - 1;
		}
		else j = next[j - 1];
	}
	return next;
}

int KMP(const char* src, const char* match) {
	if (src == NULL || match == NULL)
		return -1;
	int len_s = strlen(src);
	int len_m = strlen(match);
	int* next = getnext(match);
	int i = 0, j = 0;
	while (i < len_s && j < len_m) {
		if (src[i] == match[j]) {
			i++;
			j++;
		}
		else if (j == 0) {
			i++;
		}
		else j = next[j - 1];
	}
	if (j >= len_m)
		return i - len_m;
	else return -1;
}

int main() {
	char src[] = "abcabceabcabcdabcabcabcdabcabcfabcabc";
	char match[] = "abcabcdabcabcf";
	printf("%d\n", KMP(src, match));
}
6.1.2.2 Sunday

图文并茂!字符串匹配之Sunday、KMP和BM算法入门级讲解 - 知乎 (zhihu.com)

在检索时,对模式串等长的原串进行比较。若不匹配,则该段不可能为匹配串,此时寻找匹配串在该段中最早可能出现的位置。即将当前匹配段的下一个字符,找到其对应的匹配串中从右向左第一个字符,并与其对齐,然后再进行比较。 

①next数组记录每个字符在匹配串中最靠右的位置

void getnext(string s, int next[]) {
	for (int i = 0; i <= 255; i++) {
		next[i] = -1;
	}
	for (int i = 0; i < s.size(); i++) {
		next[s[i]] = i;
	}
}

②若不相同,该匹配串等长的段不可能为匹配串,找到当前段下一个字符,将其与匹配串最右对应位置对齐,再进行匹配这个新的等长的段。

int Sunday(string src, string match) {
	if (src.empty() || match.empty())
		return -1;
	int i = 0, j = 0;
	int next[256];
	getnext(match, next);
	int len_s = src.size();
	int len_m = match.size();
	while (i < len_s - len_m) {
		j = 0;
		while (src[i + j] == match[j]) {
			j++;
			if (j >= len_m)
				return i;
		}
		i += len_m - next[src[i + len_m]];
	}
	return -1;
}
#include <iostream>
using namespace std;
int Sunday(const char* src, const char* match) {
	if (src==NULL || match==NULL)
		return -1;
	int len_s = strlen(src);
	int len_m = strlen(match);
	int* next = (int*)malloc(sizeof(int) * 256);
	memset(next, -1, sizeof(int) * 256);
	for (int i = 0; i < len_m; i++) {
		next[match[i]] = i;
	}
	int i = 0, j = 0, k = 0;
	while (i < len_s && j < len_m) {
		if (src[i] == match[j]) {
			i++;
			j++;
		}
		else {
			//下次位置确定
			if (k + len_m < len_s) {
				i = k + len_m - next[src[k + len_m]];
				j = 0;
				k = i;
			}
			else
				return -1;
		}
	}
	return i - len_m;
}

int main() {
	char src[] = "wonwannrwawonnwonderowand";
	char match[] = "wonder";
	cout << Sunday(src, match) << endl;
}

最长回文子串(子序列)

6.1.3 在多个串中找复合条件的某个串

字典树

6.1.4 找两个串的公共部分

最长公共子串(子序列)

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值