目录
前言
这个暑假,手头上有一本关于RL的书,但是实际上数据结构和算法才是这个暑假的学习重点,两边相互进行,这篇是本人在看《大话数据结构》的读书笔记
大话数据结构的代码地址:http://cj723.cnblogs.com
并且,我找到了一些很好的激起兴趣的东西——可视化帮助——https://visualgo.net/en
在看完书以及了解清楚每种数据结构与算法之后 便是 无尽的 刷题之旅
注意
书中的代码使用C90标准代码,比如函数传入参数C++可以传入地址变量(CreateBtree(BiTree &T))但是C90只能是CreateBiTree(BiTree *T) 虽然说我打算之后看完书直接使用STL,但是这里给可能会需要自己实现的小伙伴提醒一下。(照理来说C90低端编译环境应该在高级编译环境中也可以运行)
学习STL的话有两个教程 如果中文好 如果鸟语好 当然C++Primer貌似也有STL的章节,但是只有部分关于STL的基础的描述,没有算法的详细用法介绍,推荐上面的中文教程。
程序设计=好的数据结构+算法
线性表
线性表是十分入门基础的东西,这里就不阐述,线性表可以有以下分类
- 顺序存储结构
- 链式存储结构
- 单链表
- 静态链表(用数组链式存储)
- 循环链表(末结点指向头结点)
- 双向链表(有头指针与尾指针)
PS:大话数据结构是用C写的,当不需要改变链表的值的时候,传入参数可以不用* ,直接值传递就可以了,传入复制的线性表。
栈
栈就是特殊的线性表,只允许对栈顶(链表尾)进行增删操作,OS中用于很多地方,大家比较熟悉的就是程序栈以及在算术运算用到的栈。
RPN逆波兰(后缀表达法)“9+(3-1)*3+10/2”-->“9 3 1 - 3 * + 10 2/ +"
RPN计算方法:从左到右遇到数字入栈遇到符号就将所有栈顶前两个数字进行出栈运算
中缀表达式转后缀表达式:从左向右遍历,是数字就输出,成为后缀表达式的一部分;如果是符号则与栈顶的符号比较优先级,如果是右括号或者符号优先级低于栈顶符号(例如乘除优先于加减)则栈顶元素依次出栈输出,并将当前符号进栈,一直到最终输出后缀表达式位置。
计算机处理我们通常使用的标准(中缀)表达式最重要的就是两步:
- 将中缀表达式转换成后缀表达式(此时,栈中进出的是运算的符号)
- 将后缀表达式进行运算得出结果(此时,栈中进出的是运算的数字)
队列
FIFO:插入数据队尾进行,删除数据对头进行。
顺序存储实现
为了避免出队列进行的n次前移,提出了循环队列——头尾相接(用front指针和rear指针表示头和尾,后面满了就去前面)
假设数组的最大尺寸是QueueSize,因此可以得到队列满的条件是(rear+1)%QueueSize == front,队列长度公式为(rear-front+QueueSize)%QueueSize
链式存储实现
不需要事先申请空间,就是线性表中单链表只在表头出队,表尾进队而已
总的来说队列长度确定时建议使用循环队列,无法预估队列长度的时候,则使用链队列。
人生,就像是一个很大的栈演变。出生时你赤条条地来到人世,慢慢地长大,渐渐地变老,最终还得赤条条地离开世间。
人生,又仿佛是一天一天小小的栈重现。童年父母抱你不断地进出家门,壮年你每天奔波于家庭与事业之间,老年你每天独自蹒跚于养老院的门里屋前。
人生,更需要有进栈出栈的体现。在哪里跌倒,就应该在哪里爬起来。无论陷入何等困境,只要抬头能仰望蓝天,就有希望,不断进取,你就可以让出头之日重现。困难不会永远存在,强者才能勇往直前。
人生,其实就是一个大大的队列演变。无知童年、快乐少年、稚傲青年、成熟中年、安逸晚年。
人生,又是一个又一个小小的队列重现。春夏秋冬轮回年年,早中晚循环天天。变化的是时间,不变的是你对未来执著的信念。
人生,更需要有队列精神的体现。南极到北极,不过是南纬90度到北纬90度的队列,如果你中途犹豫,临时转向,也许你就只能和企鹅相伴永远。可事实上,无论哪个方向,只要你坚持到底,你都可以到达终点。
串
- 字符串——“a1a2a3...an” e.g. “abc” a->a1 b->a2 c->a3 这里n=3
- 子串——“over”是“lover”的子串
- 空串——“”
- 空格串——“ ”
- 串的比较——ai的编码的比较(一开始ASCII后来拓展ASCII再后来为了支持其他国家(大概)有了Unicode(前256字符与ASCII兼容))
- 串的操作比起线性表更多是查找子串位置、得到指定位置子串、替换子串等操作
STL的中文教程第一个就是字符串
字符串匹配——朴素模式匹配法(一个个对比)和KMP(看毛片)算法
好马不吃回头草
KMP算法主要是为了消除没必要的回溯
假设主串为S 长度为 n 寻找子串为 T 长度为m,朴素模板匹配就是双重循环i<n,j<m每一个字符检查过去想不想等,不相等+1
但是KMP三位大佬发现如果T首字符与后面的任意一个字符都不相等,那么在判断T在某个i处是否匹配,到第j个才不匹配的话,就直接从i+j-1这个位置开始匹配。因为中间的之前匹配了,T后面的字符,T后面的字符不和第一个字符相等,那么那些S中匹配的地方必然不会和T的首字符相等,这就叫做——消除没必要的回溯。
如果T中有字符和首字符相同则也可以证明某些地方不用回溯,所以i都是只增不减的,而T中没有字符相同首字符相同的时候,下次匹配的j=1,有相同的话可以通过
确定,简单的说就是如果该位置上的字符不与开头字符匹配的话就是1,如果匹配n个开头字符,那么k就=n+1
KMP算法只在模板和主串之间存在许多“部分匹配”的情况下才体现出其优势,否则两者差异并不是很大。
/*通过计算返回子串T的next数组。*/
void get_next(String T,int *next)
{
int i,j;
i = 1;//用来对第i个位置上的next赋值
j = 0;//多少个匹配
nextp[1]=0;
while(i<T[0])/*此处T[0]表示串T的长度*/
{
if(j==0||T[i]==T[j])/*第二个条件的意思是当T[i](后缀的单个字符)和T[j](前缀的单个字符)相同的时候这时候++j,类似于上面说的根据匹配的n给next的某位赋值*/
{
++i;
++j;
next[i]=j;
}
else
j=next[j];/*如果说字符不相同的话,j回溯*/
}
}
//上面这段代码是为了计算出要匹配的T 的next数组
————————————————————————————————————————————————————————————————
/*返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0.*/
int Index_KMP(String S,String T,int pos)
{
int i = pos;
int j =1;
int next[255];
get_next(T,next);
while(i<=S[0] && j<=T[0])//第一位都存放长度
{
if(j==0||S[i]==T[j])//两字母相等则继续,与朴素算法增加了j=0的判断
{
++i;
++j;
}
else //指针后退重新开始匹配
{
j = next[j]/*j退回到合适的位置,i不回溯*/
}
}
if(j>T[0])
return i-T[0];
else
return 0;
}
但这时候其实也有一些多余的判断,比如T='aaaaax'的时候next=012345,若i=5时,T[5]和S[5]不匹配,那么j就会一直回溯到j=1,但是中间那些判断由于a都相等所以i是多余的,所以如果T串的第二三四五位置的字符都和首位的“a”相同的话,可以用首位的next[1]代替后面的next[j],所以我们可以对next的求解进行改良。
改进的next求解,就是每次得到原来的next的值之后判断前面是否有一样的元素,如果元素一样的话,此次next的值就和前面相同元素的next的值保持一致。
KMP算法告诉我们,算法不是那么难的事情,有时候,注意发现问题,注意问题特点,解决问题,说不定有一天,也有以你的名字命名的算法流传后世。
树
想当初,我大一的时候就听说有大佬已经在学二叉树了,我惊了一惊,这是什么,但是,我现在发现,其实很多东西,你不需要非得学了所有的基础再去学习,学了再说,直到你发现,有哪些东西需要补充,再去询问,补充就好了。
前面我们用小章节讲完了线性表,栈,队列,串,这些东西都比较基本,没必要花过多的时间研究,但是下面要介绍的树、图、搜索、排序就是数据结构与算法中的重点了。
我打算对每一章都单独的用CSDN总结出来,敬请期待。