数据结构和算法

1、线性表

线性表:形如:A1、A2、A3….An这样含有有限的数据序列,我们就称之为线性表
条件:第一个元素无前驱,最后一个元素无后继,其他元素都有且只有一个前驱和后继。
线性表的两种表示形式
顺序表示(其实就是数组)
链表表示
线性表一般包含如下几种操作

线性表的操作包括如下几种
  (1InitList(&L)
        构造一个空的线性表 
  (2DestroyList(&L)
       线性表存在了,消耗一个线性表
  (3ClearList(&L)
       清空线性表中的内容
  (4ListEmpty(L)
       判断线性表是否为空
  (5ListLength(L)
        返回线性表的长度
  (6GetElem(L,i,&e)
        返回线性表i位置上的元素值,通过e返回     
  (7PriorElem(L,cur_e,&pre_e)
       如果cur_e是线性表中的元素,而且不是第一个,那么我们就可以返回该元素前一个元素的值
  (8NextElem(L,cur_e,&next_e)
       如果cur_e是线性表中的元素,而且不是最后一个,就返回它下一个元素的值
  (9Listinsert(&L,i,e)
        如果线性表存在了,而且i符合条件,则在i位置插入一个元素
  (10ListDelete(&L,i)
        删除i位置上的元素
 (11ListDelete_data(&L,e,order)
        删除指定的元素e,order决定是删除一个,还是全部。
 (12Connect_two_List(L_a,L_b,& L_c)
       连接两个线性表,除去重复的内容    
 (13print(L)
       打印线性表    

学习链接

2、栈和队列

栈:后进先出的线性表,他要求只在表尾进行删除和插入操作。
表尾成为栈顶(top)
表头成为栈顶(bottom)
进栈push,出栈pop
在这里插入图片描述
在这里插入图片描述
栈的基本操作主要有:栈的初始化、判空、判满、取栈顶元素、在栈顶进行插入和删除。
在栈顶插入元素称为入栈;
在栈顶删除元素称为出栈。
栈和线性表类似,也有两种存储表示方法顺序栈和链栈,链栈的操作是线性表操作的特例,操作比较容易实现。顺序栈即栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素在顺序栈中的位置,top = 0表示空栈。由于栈在使用的过程中所需要的大小难以估计,所以通常是先为栈分配一个基本容量,然后再使用的过程中,当栈的空间不够使用的时候再继续追加存储空间。
栈的初始化操作为:按照设定的初始分配量进行第一次存储分配,这里使用malloc()函数来分配存储空间。base作为栈底指针,它始终指向栈底,所以s.top = s.base可以作为栈空的标记。top为栈顶指针,top的初值指向栈底。每当插入一个元素时top加1,弹出一个元素时top减1,因此,非空栈中的栈顶指针始终在栈顶元素的下一个位置上。
在这里插入图片描述

#define STACK_INIT_SIZE 100
//初始化顺序栈,构造一个空栈
Status InitStack(SqStack &S){
	//分配存储空间 
	S.base = (SDataType *)malloc(STACK_INIT_SIZE*sizeof(SDataType));
	if(!S.base){
		//如果分配失败,则返回error 
		return OVERFLOW;
	}
	//S.top 始终指向栈顶元素的下一个位置 
	S.top = S.base;    //初始状态下为空栈 
	S.StackSize = STACK_INIT_SIZE;//当前已经分配的存储容量为100个 
	return OK;	
}

判断是否为空栈
当我们弹出栈顶元素时,往往需要判断一下栈是否为空来防止发生下溢。上面我们说到base作为栈底指针,它始终指向栈底,所以s.top = s.base可以作为栈空的标记。所以我们可以这样判断栈是否为空.
判断是否为满栈
当我们使一个元素入栈的之前,我们往往需要判断一下栈是否为满栈,防止发生上溢的情况。因为我们定义了一个StackSize来表示当前已经分配的存储空间,所以我们可以用s.top - s.base 来算出当前已经使用的栈空间。所以当s.top - s.base == s.StackSize时表示已经满栈
入栈
入栈时我们首先要判断栈是否为满栈,如果为满栈我们要首先realloc()函数追加存储空间,然后才能将元素入栈。

#define STACKINCREMENT 10
//入栈
Status Push(SqStack &s,SDataType e){
	SDataType *p;
	//首先判断栈是不是满的(上溢) 
	if(s.top-s.base == s.StackSize){
		//追加空间 
		p = (SDataType *)realloc(s.base,(STACK_INIT_SIZE+STACKINCREMENT)*sizeof(SDataType));
		if(!p){
			//如果没有找到符合条件的存储空间,则返回error 
			return OVERFLOW;
		}
		//成功找到则使s.base指向p 
		s.base = p;
		s.top = s.base + s.StackSize;
		s.StackSize +=  STACKINCREMENT;
	}
	//先插入元素,然后将栈顶指针加 1 
	*(s.top) = e;
	s.top++;
	return OK;
}

出栈
出栈时我们首先要判断栈是否为空栈。如果栈已经空了,则返回error。

//出栈
Status Pop(SqStack &s,SDataType &e){
	//判断是否会发生下溢 
	if(s.top != s.base){
		s.top--;    //先将栈顶指针减 1 
		e = *(s.top);
	}else{
		return 0;
	}
	return e;
}

学习链接
逆波兰表达式又叫做后缀表达式
实现逆波兰计算器要分为两个步骤:
1)、将算数表达式(中缀表达式)转换为逆波兰表达式(后缀表达式)
规则:从左到右遍历中缀表达式的每个数字和符号,1、若是数字或小数点则直接输出;2、如果是右括号,则出栈输出,一直到左括号结束;3、若是符号,则判断其与栈顶符号的优先级,栈顶符号是右括号或者优先级不高于栈顶符号,则栈顶元素依次出栈并输出,直到遇到左括号或栈空才将该符号入栈。如果优先级高于栈顶符号,则直接入栈。
2)、用栈实现逆波兰表达式的计算;
在这里插入图片描述
队列
队列也是一种线性表,是一种先进先出的线性结构。队列只允许在表的一端进行插入(入队)、删除(出队)操作。允许插入的一端称为队尾,允许删除的一端称为队头。

初始化队列:InitQueue(Q)
   操作前提:Q为未初始化的队列。
   操作结果:将Q初始化为一个空队列。
判断队列是否为空:IsEmpty(Q)
   操作前提:队列Q已经存在。
   操作结果:若队列为空则返回1,否则返回0。
判断队列是否已满:IsFull(Q)
   操作前提:队列Q已经存在。
   操作结果:若队列为满则返回1,否则返回0。
入队操作:EnterQueue(Q,data)
   操作前提:队列Q已经存在。
   操作结果:在队列Q的队尾插入data。
出队操作:DeleteQueue(Q,&data)
   操作前提:队列Q已经存在且非空。
   操作结果:将队列Q的队头元素出队,并使用data带回出队元素的值。
取队首元素:GetHead(Q,&data)
   操作前提:队列Q已经存在且非空。
   操作结果:若队列为空则返回1,否则返回0。
清空队列:ClearQueue(&Q)
   操作前提:队列Q已经存在。
   操作结果:将Q置为空队列。

队列有两种存储形式:顺序存储和链式存储。
链式队列使用链表来实现,链表中的数据域用来存放队列中的元素,指针域用来存放队列中下一个元素的地址,同时使用队头指针指向队列的第一个元素和最后一个元素。
采用顺序队列存储的队列称为顺序队列,采用链式存储的队列称为链式队列。顺序队列采用数组存储队列中的元素,使用两个指针尾指针(rear)和头指针(front)分别指向队列的队头和队尾。使用顺序队列由于在操作时会出现“假溢出现象”,所以可以使用顺序循环队列合理的使用队列空间。
学习链接

3、递归和分治思想

递归,首先它的目的是把问题缩小为同类问题的子问题,通过不断地递归调用自身,最终到达某次调用能结束返回。
条件:递归到一定程度必须可以终止,不能无限地递归,换句话说,就是递归函数一定是可以结束的。

分治,对于一个规模为N的问题,若该问题可以容易解决,则直接解决,否则将其分解为M个规模较小的子问题,这些问题相互独立(这点很重要),并且和原问题形式相同,递归解决这些子问题,然后各子问题的解得到原问题的解。其运用的手段是递归思想。
条件:可以分解成几个相互独立的子问题。

4、字符串

字符串或串(String)是由数字、字母、下划线组成的一串字符。一般记为 s=“a1a2···an”(n>=0)。它是编程语言中表示文本的数据类型。在程序设计中,字符串(string)为符号或数值的一个连续序列,如符号串(一串字符)或二进制数字串(一串二进制数字)。
补充:字符串在存储上类似字符数组,它每一位单个元素都是能提取的,字符串的零位是它的长度,如s[0]=10

字符串操作
strcpy(p, p1) 复制字符串
strncpy(p, p1, n) 复制指定长度字符串
strcat(p, p1) 附加字符串
strncat(p, p1, n) 附加指定长度字符串
strlen§ 取字符串长度
strcmp(p, p1) 比较字符串
strcasecmp忽略大小写比较字符串
strncmp(p, p1, n) 比较指定长度字符串
strchr(p, c) 在字符串中查找指定字符
strrchr(p, c) 在字符串中反向查找
strstr(p, p1) 查找字符串
strpbrk(p, p1) 以目标字符串的所有字符作为集合,在当前字符串查找该集合的任一元素
strspn(p, p1) 以目标字符串的所有字符作为集合,在当前字符串查找不属于该集合的任一元素的偏移
strcspn(p, p1) 以目标字符串的所有字符作为集合,在当前字符串查找属于该集合的任一元素的偏移

5、KMP算法

KMP算法:主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。核心是避免不必要的回溯,问题由模式串决定,不是由目标决定的。
学习资料

6、树

线性表、栈、队列、串是一对一的数据结构,而树是一对多的数据结构
树(Tree)是n(n≥0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:(1)有且仅有一个特定的称为根(Root)的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、……、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)
在这里插入图片描述
树的定义还需要强调两点:
(1)b>0时根结点是唯一的,不可能存在多个根结点,别和现实中的大树混在一起,现实中的大树有很多根须,那是真实的树,数据结构中的树只能有一个根结点。
(2)m>0时,子树的个数没有限制,但它们一定是互不相交的。
在这里插入图片描述
树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree)。

  • 度为0的结点称为叶结点(Leaf)或终端结点
  • 度不为0的结点称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值
    在这里插入图片描述
    树结点的度的最大值是结点D的度,为3,所以树的度也为3。
    结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲。同一个双亲的孩子之间互称为兄弟(Sibling)。结点的祖先是从根到该结点所经分支上的所有结点。所以对于H来说,D、B、A都是它的祖先。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。B的子孙有D、G、H、I、J,
    在这里插入图片描述
    结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。若某结点在第l层,则其子树的根就在第l+1层。其双亲在同一层的结点互为堂兄弟。显然图6-2-6中,D、E、F是堂兄弟,而G、H、I、J也是。树中结点的最大层次称为树的深度(Depth)或高度,当前树的深度为4。
    如果将树中结点的子树看成从左至右是有次序的,不能互换的,则该树称为有序树,否则称为无序树。
    森林是m(m>0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。
    在这里插入图片描述
    树的存储结构:顺序存储和链式存储结构。
    这里介绍三种不同的表示法:双亲表示法、孩子表示法、孩子兄弟表示法
    学习链接

7、二叉树

二叉树的特点有:
1)每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。注意不是只有两棵子树,而是最多有。没有子树或者有一棵子树也是可以的。
2)左子树和右子树 是有顺序的,次序不能任意颠倒。
3)即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。如下图,树1和树2是同一棵树,但它们却是不同的二叉树。

二叉树具有五种基本形态:
1)空二叉树;
2)只有一个根结点;
3)根结点只有左子树;
4)根结点只有右子树;
5)根结点既有左子树又有右子树。
在这里插入图片描述
学习链接
特殊二叉树
1)斜树
斜树一定是斜的,但往哪里斜还是有讲究的。所有结点都只有左子树的二叉树叫左斜树。所有结点都只有右子树的二叉树叫右斜树。这两者统称为斜树。
2)满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
3)完全二叉树
在这里插入图片描述
在这里插入图片描述
树、森林与二叉树的转换
树转换为二叉树
1)加线。在所有兄弟结点之间加一条线。
2)去线 。对树中每个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线。
3)层次调整。以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。注意第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子 是右孩子。
在这里插入图片描述
森林转换为二叉树
森林是由若干树组成的,所以完全可以理解为:森林中的每一棵树都是兄弟,可以按照兄弟的处理办法来操作 。步骤如下:
1)把每个树转换为二叉树。
2)第一棵二叉树不动,从第二棵二叉树开始,依次把一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,用线连接起来。当所有的二叉树连接起来后,就得到了由森林转换来的二叉树。
在这里插入图片描述
二叉树转换为树
二叉树转换为树是转换为二叉树的逆过程,也就是反过来做而已。如下图,具体不步骤如下:
1)加线。若某结点的右孩子结点存在,则将这个结点的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点……,即左孩子的n个右孩子结点都作为此结点的孩子。将该结点与这些右孩子结点用线连接起来。
2)去线。删除原二叉树中所有结点与其右孩子结点的连线。
3)层次调整。使其结构层次分明。
在这里插入图片描述
二叉树转换为森林
判断一棵树可以转换成一棵树还是森林,标准其实很简单,那就是只要看这棵二叉树的根结点有没有右孩子,有就是森林,没有就是一棵树。二叉树转换成森林的步骤如下:
1)从根结点开始,若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树,若右孩子存在,则连线删除……,直到所有右孩子的连线都删除为止。
2)再将每棵分离后的二叉树转换为树即可。
在这里插入图片描述
赫尔曼树
在这里插入图片描述
赫夫曼定义,从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称做路径的长度。上图的二叉树a中,根结点到结点D的路径长度为4,二叉树中根结点到结点D的路径长度为2。树的路径长度就是从树根到每一结点的路径长度之和。二叉树a的树路径长度就为:1+1+2+2+3+3+4+4=20。二叉树b的路径长度就为:1+2+3+3+2+1+2+2=16。
如果考虑到带权的结点,结点的带权的路径长度为从该结点到树根之间的路径长度与结点上权的乘积。树的带权路径长度为树中的所有子结点的带权路径长度之和。假设有n个权值(w1,w2,……,wn),构造一棵有n个叶子结点的二叉树,每个叶子结点带权wk,每个叶子结点的路径长度为lk,我们通常记作,则其中带权路径长度WPL最小的二叉树称作赫夫曼树。有些书中称为最优二叉树。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值