2021-01-24

这篇学习笔记详细介绍了数据结构中的栈和队列,包括它们的定义、基本操作及其实现。此外,还讨论了两种经典的字符串搜索算法——BF算法和KMP算法,并解释了它们的工作原理。最后,提到了二分查找算法以及位运算的概念和应用。
摘要由CSDN通过智能技术生成

1.17~1.24学习笔记

栈与队列
一.栈
1.栈的定义:栈(Stack)是一种只能在一端进行插入或删除操作的线性表(后进先出)。
2.顺序栈的基本操作
初始化

void InitStack(Stack *S){
	//初始化顺序栈即使栈顶指针为-1 
	S->Top = -1;	
}
	栈判空
int StackEmpty(Stack *S) {
	//栈判空函数
	if(S->Top == -1)	//栈空 
		return 1;
	else				//非空 
		return 0; 
}
	进栈
int Push(Stack *S, int e){
	//进栈函数
	if(S->Top == StackMaxSize-1)//栈满
		return ERROR; 
	else{
		S->Data[++(S->Top)] = e;	//栈顶指针加一,存储元素 
		return OK; 
	}
} 
	出栈
int Pop(Stack *S, int *e){
	//出栈并将元素传给e 
	if(S->Top == -1)	//栈空
		return ERROR;
	else{
		*e = S->Data[S->Top--];//传递元素,栈顶指针减一 
		return OK;		
	} 
}

二.队列
1.队列的定义: 队列也是一种操作受限的线性表,和栈不同的是,队列只允许在一段插入在另一端取出(先进先出)。
2.队列的基本操作
定义

#define QueueMaxSize	10
#define ERROR		0
#define OK			1

typedef struct{
	int Data[QueueMaxSize];//存放队列元素 
	int frond;	//队头指针 
	int rear;	//队尾指针 
}Queue; 
	初始化
void InitQueue(Queue *Q){
	//初始化循环队列
	Q->frond = Q->rear = 0; 
} 
	判空
int isEmpty(Queue Q){
	//循环队列判空
	if(Q.frond == Q.rear)//队列空 
		return OK;
	else
		return ERROR; 	//队列非空 
}
	入队
int EnQueue(Queue *Q, int e){
	//入队操作
	if((Q->rear+1)%QueueMaxSize == Q->frond)//队满 
		return ERROR;
	else{
		Q->Data[Q->rear] = e;
		Q->rear = (Q->rear + 1)% QueueMaxSize;//队尾指针加一取模 
		return OK;
	} 
} 

出队

int DeQueue(Queue *Q, int *e){
	//出队操作
	if(isEmpty(*Q) == OK)//队空报错 
		return ERROR;
	else{
		*e = Q->Data[Q->frond];
		Q->frond = (Q->frond + 1)%QueueMaxSize;//队头指针加一取模 
		return OK;
	}
} 

BF算法和KMP算法
一.BF算法
算法思想:比较简单,定义i,和j分别指向主串和字串的头部),依次向后比较,若比较失败,主串和字串都需要回溯(i=比较轮数+1的位置,j回到0位置)。

#include<stdio.h>
#include<string.h>
bool BF(char a[],char b[]){
	int index1 = 0;//指向a的头部 
	int index2 = 0;//指向b的头部 
	for(int i = 0;i<strlen(a);i++){//轮数 
		if(a[index1]==b[index2]){
			index1++;
			index2++;
		}else{
			index1 = i+1;
			index2 = 0;
		}
		if(index2==strlen(b)) return true; 
	}
	  return false;
}
int main(){
	char a[6] = {'a','c','a','b','d','a'};
	char b[3] = {'a','b','d'};
	char c[3] = {'a','b','e'};
	if(BF(a,b))printf("匹配\n");
	else printf("不匹配\n");
	if(BF(a,c))printf("匹配\n");
	else printf("不匹配\n");
	return 0;
} 

二.KMP算法
BF算法弊端:每次j下标都要回到0号下标,当主串和字串匹配失败时,主串进行回溯会影响效率,回溯之后,主串与字串有些部分比较是没有必要的。
next数组:next数组是字符串每一位及之前的字符串中,前缀和后缀公共部分的最大长度的集合(前缀是第一个元素到倒数第二个元素,后缀是第二个元素到倒数第一个元素)。
例如字符串(abxab):next[0]=-1(无前后缀)
next[1]=0(ab无公共前后缀)
next[2]=0(abx中无公共前后缀)
next[3]=1(abxa公共前后缀最长为a,长度为1)
next[4]=2(abxab公共前后缀最长为ab,长度为2)

#include<stdio.h>
#include<string.h>
void getNext(char a[],int *next){
	next[0]=-1;
	int k = -1;
  for (int q = 1; q < strlen(a); q++)
    {
        while (k > -1 && a[k + 1] != a[q])
        {
            k = next[k];//往前回溯
        }
        if (a[k + 1] == a[q])//如果相同,k++
        {
            k = k + 1;
        }
        next[q] = k;
    }
}
bool KMP(char a[],char b[]){
    int next[strlen(a)];
	getNext(a,next);
	int i = 0;
	int j = 0;
	int len1 = strlen(a);
	int len2 = strlen(b);
	while(i<len1&&j<len2){
		if(a[i]==b[j]||j==-1){ //注意一定要j = -1,前后公共部分长度为0,就没有必要回溯了
			i++;
			j++;
		}else{
			j = next[j];
		}
	}
//	printf("%d,%d ",i,j);
	if(j==strlen(b)) return true;
	return false;
}
int main(){
	char a[] = "abacababa"; 
	int next[strlen(a)];
	getNext(a,next);
	//for(int i = 0;i< strlen(a);i++)printf("%d ",next[i]);
	char b[] = "cba";
	if(KMP(a,b))printf("匹配\n");
	else printf("不匹配\n");

	return 0;
}

二分
1.整数二分:

#include<iostream>

using namespace std;
const int N=10010;
int main()
{
	int a[N],n,q,k;
	scanf("%d%d",&n,&q);
	for(int i=0;i<n;i++)
	{
		cin >> a[i];
	}
	while(q--)
	{
		int l=0,r=n-1;
		cin >> k;
		while(l<r)
		{
			int mid=(l+r)/2;
			if(a[mid]>=k) r=mid;
			else l=mid+1;
		}
		if(a[l]!=k) cout <<"-1 -1"<< endl;
		else 
		{
			cout << r << " ";
			int r=n-1;
			while(l<r)
			{
				int mid=(l+r+1)/2;
				if(a[mid]<=k) l=mid;
				else r=mid-1;
			}
			cout << r<< endl;
		} 
	}
	return 0;
 } 

浮点数二分:

#include<iostream>

using namespace std;
int main()
{
	double n;
	cin >> n;
	double l=100,r=-100;
	while(r-1>1e-8)
	{
		double mid=(l+r)/2;
		if(mid*mid*mid>=n) r=mid;
		else l=mid;
	}
	printf("%.6lf/n",l);
	return 0;
}

位运算
1.按位与“&”
将参与运算的两操作数各对应的二进制位进行与 操作,只有对应的两个二进位均为1时,结果的对 应二进制位才为1,否则为0。
2.按位或“|”
将参与运算的两操作数各对应的二进制位进行或操 作,只有对应的两个二进位都为0时,结果的对应 二进制位才是0,否则为1。
3.按位异或“^”
将参与运算的两操作数各对应的二进制位进行异或操作, 即只有对应的两个二进位不相同时,结果的对应二进制 位才是1,否则为0。 例如:表达式“21 ^ 18 ”的值是7(即二进制数111)。
4.按位非“~”
按位非运算符“~”是单目运算符。 其功能是将操作数中的二进制位0变成1,1变成0。
5.左移运算符“<<”
a<<b:将a各二进位全部左移b位后得到的值。左移时,高位 丢弃,低位补0。a 的值不因运算而改变。(等同于乘以二)
6.右移运算符“>>”
a>>b:将a各二进位全部右移b位后得到的值。右移时,移出最右边 的位就被丢弃。 a 的值不因运算而改变。(等同于除以二)
7.有符号整型
正数:0 负数:1
1:00000000…01
-1:100000000…01
8.反码
-7:100000000…111 反码:01111111…000
9.补码
正数的补码等于原码,负数的补码就是在反码的基础上加1。
0111111111…000+1=01111111111…001
前缀和与差分
前缀和与差分是互为逆运算的两种计算方式,前缀和指的是一个数组是另一个数组中前n项元素之和,而差分指的是一个数组的前n项的和是另一个数组。实际上就是,如果数组a是数组b的前缀和,那b就是a的差分。

  1. 前缀和
    假设a[5]={1,2,3,4,5},那么b[5]={1,3,6,10,15},即a[n]=b[1]+b[2]+b[3]+…+b[n-1]+b[n]
    通过前缀和,我们可以快速的求出原数组从l到r的区间中元素的和,等于s[l ~ r] = b[r] - b[l - 1]。
    代码:
#include <iostream>

using namespace std;

const int N = 1e5 + 1;

int n, m;
int a[N], s[N];

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    for (int i = 1; i <= n; ++i) s[i]= s[i - 1] + a[i];
    while (m--) {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", s[r] - s[l - 1]);
    }
}

2.差分
差分是前缀和的逆运算,数组a[n]和它的差分数组b[n]有着如下关系:
a[1] = b[1]
a[2] = b[1] + b[2]
a[3] = b[1] + b[2] + b[3]

a[n - 1] = b[1] + b[2] + b[3] + … + b[n - 2] + b[n - 1]
a[n] = b[1] + b[2] + b[3] + … + b[n - 2] + b[n - 1] + b[n]
​ 由此可见,如果b是a的差分数组,那a就是b的前缀和数组,通过差分,我们可以快速的实现对一个区间内的所有元素的同增同减操作。
​ 假设我们要让a[1]到a[9]的元素全部+1,我们只需要让b[1] += 1,b[10] -= 1即可。
代码:

#include <iostream>

using namespace std;

const int N = 100010;

int a[N], b[N];

int n, m;

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) b[i] = a[i] - a[i - 1];
    while (m--) {
        int l, r, c;
        scanf("%d%d%d", &l, &r, &c);
        b[l] += c;
        b[r + 1] -= c;
    }
    for (int i = 1; i <= n; ++i) {
        a[i] = a[i - 1] + b[i];
        printf("%d ", a[i]);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值