- 线性表
- 顺序线性表的数据结构
大概就是
Struct SqList{
ElemType* elem;//这个是本体
Int iLength;//长度
};
因为是指针形式的,所以一定要记得改指针的值;
顺序表也有几个重要的算法,比如插入,删除。取下标为i的值
顺序表的优点是可以随机存取,缺点是插入删除的时间复杂度大。
插入的时候,在插入位置后面的元素都要等于前面的元素,而且是从后往前。
删除则相反。从前往后删。
同时记得iLength--;
- 链表的数据结构和算法
数据结构
Struct Lnode{
ElemType data;
Lnode* next;//指向下一个结点
}*LinkList;
一定要注意递归调用。Ptr改成next
链表分为带头结点的和不带头结点的
带头结点的,顾名思义,就是LinkList->next,此时指向的结点才是首元结点;
不带头结点,那么LinkList这个头结点本身指向的就是首元结点。
一般,带头结点的初始化就是
LinkList=new Lnode;
LinkList->next=nullptr;//先将首元结点清空为null;
要注意清空和摧毁链表的区别
清空是保留头结点,要用两个变量,一个赋值首元结点,一个赋值成首元结点,一个是赋值成正在delete的那个结点。
取链表中第i个元素
因为链表不能进行随机存取,所以第取i个元素,只能从头开始遍历。
删除插入第i个元素
同理,与上面一样,先要从头开始遍历,一直遍历到第i-1个元素。
注意他是怎么遍历的!
先Lnode* p=LinkList->next;//先让他等于首元结点
j=0;
然后while(p && j<i-1){p=p->next;j++;}//这是遍历到第i-1个元素
然后下面进行判断
If(!p || j>i) !p是说明遍历到头了 j>i说明给的数是负数!
插入的时候,假设p是待插入的结点
- >next=LinkList->next;
LinkList->next=p;//这样即可实现插入了;
删除的话,也是先进性遍历,遍历到指定位置的前一个。用一个元素存一下
P=LinkList->next;
LinkList->next=p->next;
Delete p;这样即可
接下来就是头插法和尾插法
头插法不需要额外增加新的变量,因为头是固定的,而尾插法需要增加一个Lnode* 的变量,用来存储尾结点。
头插法就是P->next=L->next;L->next=p;
尾插法先弄个尾巴变量r r->next=p;p->next=nullptr;r=p;//修改尾巴变量;
- 双向链表、循环链表
循环列表,就是接着的
可以从任意一个结点出发找到其他结点,没必要非得从头结点出发了
所谓双向链表,就是有两个指针域,一个是next后继,一个是prior前驱元素。
这样插入删除就非常方便了,但是是一种利用空间换取时间的一种存储形式。
最后,总复习进行比较
所谓堆栈、队列
就是线性表,但是,是有一定限制的线性表。
栈是FILO先进后出的线性表。即限制了他进出要先进栈的后出去。
而队列FIFO,限制了他先进要后出的结构。
- 栈的数据结构和算法
数据结构
因为是线性表,所以栈也有顺序栈和链栈
一般来说是顺序栈,链栈不多见。
数据结构
Struct SqStack{
ElemType* base;//栈底
ElemType* top;//栈顶
};
两个指针,一个存栈顶,一个是栈底;
初始化的时候
Base=new ElemType[xx];
Top=base,这样当他们相等的时候,就是为空了.
栈的两个重要操作就是push和pop操作
Push操作
Push,即入栈,首先进行判断,是不是已经到栈顶了,没有的话直接
*(top++)=e;e是待进栈的元素;
Pop操作
Pop就是出栈,首先进行判断,是不是已经栈空了,如果不是那么就直接出栈就好了
Top--;直接这样即可;
- 队列的数据结构和算法
队列也分为顺序队列和链队列;
一般来说使用顺序循环队列。
顺序循环队列就是下标首和尾是链接起来的;
还有很多定义,比如双向队列等等
单链队列的数据结构
Struct QueueNode{
ElemType* data;
QueueNode* next;
}*QueuePtr;//注意 这里和定义链表还是完全一样的
Struct QueueLink{
QueuePtr* front;//对头
QueuePtr* rear;//队尾;
}
循环顺序链表的数据结构
Struct SqQueue{
ElemType* data;
Int front;
Int rear;
}
注意,循环列表区别队满和队空的方法很多,我们这里用空出一个元素来表示。
rear==front,代表空,(rear+1)%QueueLength==front;代表队满;
顺序队列的几个重要算法
进队列
先判断是不是队满,队满的判断参照上面如何判断的
然后Q.data[Q.rear]=e;
- rear=(Q.rear+1)%SIZE;
出队列
先判断是不是队空
Q.front=(Q.front+1)%SIZE;
初始化
初始化那就是先把data更新一下,地址是可读写的
然后把rear=front即可
总结一下,队列和栈都是线性表,只是限制了进出的顺序。
而且加了一些新的东西
比如栈,是直接两个指针
而队列,(顺序循环队列),则在顺序表的基础上,加了两个int变量rear和front
所谓字符串,其实也是线性表,简而言之,就是限定了存储类型的线性表。
比如字符串,限定必须储存char。
字符串最重要的两个算法。
串的模式匹配算法
BF和KMP算法
BF是传统的,暴力枚举,效率较低。
而KMP的时间效率高O(m+n)
下面简单的举出这两种算法
首先,字符串的数据结构
一般来说,T[0]是来存储字符串长度的
首先,暴力BF算法的字串匹配
算法思想就是从字串的第一个与主串的第一个进行比较
一个字符一个字符地进行比较,匹配指针指向下一个,不匹配那么就指向
一旦不匹配,那么子串指针都回退到0 主串的指针经过计算回退到某个位置(上次比较的下个位置)其实就是i=i-(j-1)+1=i-j+2;//这样就不用在另设个空间了
Int Sstring::Index_BF(Sstring s){//c++形式 this->T代表主字符串
//s是字串
Int i=1,j=1;
While(i<=s.ch[0] && j<=this->ch[0]){
If(s.ch[j]==this->ch[i]){i++;j++}
else{j=1; i=i-j+2; }
}
If(j>s.ch[0]) return i-t.ch[0]//用找到的i-字串长度 就是起始位置了
}
其次 难度较高的KMP算法的子串匹配
KMP算法的思想是主串的指针不回退,
那不会退的话会不会遗漏呢?
答案是且字串的指针回退也是经过Next[i]计算的
这样,子串巧妙的回退,就不会有遗漏了。
比如acabaabaabca
子串abaabca
先匹配
第一个可以
第二个不行,那么子串指针回退,回退到看看前缀的后缀有多少相等的(最大)
回退的位置是这个值+1;
如果没有,即0,那么就是1,也就是回退到0;
比如
acabaabaa...
abaabc 这个时候出现不匹配的情况 要进行回退
而这个时候 子串的前缀ab 和后缀ab相等,那么就是要回退到第三个位置再进行比较
acabaabaa
(ab)aa....这样来进行比较
假定next[j]的数组已经被求出来 那么KMP的算法就是
Int Sstring::Index_KMP(Sstring s){//c++形式 this->T代表主字符串
//s是字串
Int i=1,j=1;
While(i<=s.ch[0] && j<=this->ch[0]){
If(j==0 || s.ch[j]==this->ch[i]){i++;j++}
else{j=next[j]; }//其他的都是很一样
}
If(j>s.ch[0]) return i-t.ch[0]//用找到的i-字串长度 就是起始位置了
}
至于next数组到底怎么求,这个比较难,
下面贴出算法
Void Get_next(){
I=1;
next[1]=0;
j=0;
While(i<this->ch[0]){
//小于长度
If(j==0 ||this->ch[i]==this->ch[j] ){i++;j++ next[i]=j;
Else{
J=next[j];
}
}
}
- 树
用的最广的就是二叉树、霍夫曼树
树是一种一对多的数据结构。之前学的都是一个元素
只有一个前驱或者后继元素。但是树可以有多个后继。1:n的数据结构
有几个后继就叫做几叉树;最常用的是二叉树。
二叉树相关概念和性质
孩子、双亲。双亲的指针域指向孩子。
满二叉树:就是深度为k 结点为2^k-1的二叉树
完全二叉树:就是满二叉树,按照逆顺序去掉结点。
二叉树的数据结构
同理,二叉树也有顺序的和链表的。
按照顺序储存的二叉树:
ElemType SqBiTree[SIZE];
直接这样即可,下标就是按照满二叉树(完全二叉树)来进行顺序排序。
如果是一般的二叉树,那么对照着满二叉树,再原有的空缺的下标填0;
按照链式存储的二叉树:
数据结构
Struct BiTNode{
ElemType data;
Struct BiTNode* lChild,rChild;
}*BiTree;
这个就是链表存储二叉树的数据结构
遍历二叉树
二叉树尤为重要,二叉树分为3中遍历方式
前序、中序、后序遍历。
这里用递归的调用是最简单的。
也可以采取非递归的,
比如用队列存储(层次遍历)
前序遍历
掌握前序遍历就可以掌握了其他两种遍历
Void PreOrderTraverse(BiTree T){
If(T){
//t不为空
Visit(T);//前序遍历,先访问头结点
PreOrderTraverse(T->lChild);//递归 想改变顺序 只需要将Visit(T)改变位置即可
PreOrderTraverse(BiTree->rChild);
}
}
层次遍历
用到了队列,不管什么前中后了
利用队列先进先出的性质,一层一层地进去。进队列,然后出队列访问,顺便把他左右孩子进队列。等到队列空的时候,那么久直接访问完成了;
Void LeverTraverse(BiTree T){
Queue Q;
- EnQue(T);
While(!Q.empty()){
auto e=Q.Deque;
Visit(e);
Q.enque(e.lChild);
Q.enque(e.rChild);//左右孩子进队列
}
}