数据结构
数据结构
数据结构
包含:数组、栈(stack)、队列、链表、树、图、堆(Heap)、散列表(Hash)
1.数据结构存储一般常用的有几种?各有什么特点?
数据存储的结构一般有两种:顺序存储结构、链式存储结构
- 顺序存储结构:
比如说数组,1-2-3-4-5-6-7-8-9-10存储是按顺序的。再比如栈、队列都是顺序存储结构 - 链式存储结构:
同数组顺序存储1-2-3-4-5-6-7-8-9-10不同,链式存储为 1(地址)-2(地址)-7(地址)-4(地址)-5(地址)-9(地址)-8(地址)-3(地址)-6(地址)-10(地址)。每个数字后面跟着一个地址 而且存储形式不再是顺序
2.集合结构、线性结构、树形结构、图形结构
-
集合结构
集合结构是一种松散的逻辑结构。元素同元素之间没有任何关系。唯一专用于集合类型的数据结构是哈希表。 -
线性结构
线性结构是一个有序数据元素的集合。链表是线性表的其中之一。 -
树形结构
树形结构指的是数据元素之间存在着“一对多”的树形关系的数据结构,是一类重要的非线性数据结构。 -
图形结构
图形结构是一种比树形结构更复杂的非线性结构。在树形结构中,结点间具有分支层次关系,每一层上的结点只能和上一层中的至多一个结点相关,但可能和下一层的多个结点相关。而在图形结构中,任意两个结点之间都可能相关,即结点之间的邻接关系可以是任意的 [2] 。
3.单向链表、双向链表、循环链表
- 单向链表(有个变异叫单向循环列表)
A->B->C->D->E->F->G->H
上图就是单链表的存储结构原理图,由图中我们可以看出每个节点都由两部分构成,一个是data数据域、另一个是指向下一个节点的next指针域,指针域不存放任何数据。然后一直通过这样的方式一直指下去,最后就形成了一条类似铁链的结构,所以称为链表。我们看到最后的next指针为null,说明到了最后一个节点,最后一个节点的指针域不指向任何节点,所以next=null。
单向循环列表:链表中最后一个节点的next域不再为None,而是指向链表的头节点。
-
双向链表
双向链表与单链表比较就是多了一个指向前驱节点的指针,这样来构成有序性。 -
循环链表
循环链表是与单向链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。发挥想象力 A->B->C->D->E->F->G->H->A. 绕成一个圈。就像蛇吃自己的这就是循环 不需要去死记硬背哪些理论知识。
4.数组同链表的区别
-
数组
数组元素在内存上连续存放,可以通过下标查找元素。查找、删除元素需要移动大量元素,比较适用于元素很少变化的情况。 -
链表
链表中数据元素离散存储,查找慢,查找、删除只需对元素指针重新赋值,效率高。
数组——对象数量较少且数量固定时优先考虑;
字典——需要进行频繁的搜索时优先考虑;
链表——对象数量是动态的且搜索不是优先选项时,需要经常增减节点的情况优先考虑;
5.数组、栈(stack)、队列、链表、树、图、堆(Heap)、散列表(Hash)
-
数组
-
栈
是一种特殊的线性表,它只能在一个表的一个固定端进行数据结点的插入和删除操作。栈按照后进先出的原则来存储数据,也就是说,先插入的数据将被压入栈底,最后插入的数据在栈顶,读出数据时,从栈顶开始逐个读出。栈在汇编语言程序中,经常用于重要数据的现场保护。栈中没有数据时,称为空栈。特点:
- 栈是限定仅在表尾进行插入和删除操作的线性表。我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。栈的特殊之处在于它限制了这个线性表的插入和删除位置,它始终只在栈顶进行。
- 栈是一种具有后进先出的数据结构,又称为后进先出的线性表,简称 LIFO(Last In First Out)结构。也就是说后存放的先取,先存放的后取,这就类似于我们要在取放在箱子底部的东西(放进去比较早的物体),我们首先要移开压在它上面的物体(放进去比较晚的物体)。
- 堆栈中定义了一些操作。两个最重要的是PUSH和POP。PUSH操作在堆栈的顶部加入一个元素。POP操作相反,在堆栈顶部移去一个元素,并将堆栈的大小减一。
- 栈的应用—递归
-
队列
- 队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。允许插入的一端称为队尾,允许删除的一端称为队头。它是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作,和栈一样,队列是一种操作受限制的线性表。
- 队列是一种先进先出的数据结构,又称为先进先出的线性表,简称 FIFO(First In First Out)结构。也就是说先放的先取,后放的后取,就如同行李过安检的时候,先放进去的行李在另一端总是先出来,后放入的行李会在最后面出来。
-
链表( Linked List)
链表是一种数据元素按照链式存储结构进行存储的数据结构,这种存储结构具有在物理上存在非连续的特点。链表由一系列数据结点构成,每个数据结点包括数据域和指针域两部分。其中,指针域保存了数据结构中下一个元素存放的地址。链表结构中数据元素的逻辑顺序是通过链表中的指针链接次序来实现的。 -
树( Tree)
树是典型的非线性结构,它是包括,2个结点的有穷集合K。在树结构中,有且仅有一个根结点,该结点没有前驱结点。在树结构中的其他结点都有且仅有一个前驱结点,而且可以有两个后继结点,m≥0。 -
图(Graph)
图是另一种非线性数据结构。在图结构中,数据结点一般称为顶点,而边是顶点的有序偶对。如果两个顶点之间存在一条边,那么就表示这两个顶点具有相邻关系。 -
堆(Heap)
堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。(完全二叉树下面有介绍)
堆满足以下两个性质:
1. 堆中某个节点的值总是不大于或不小于其父节点的值。
2. 堆总是一颗完整的二叉树
完全二叉树的特点是:
1. 只允许最后一层有空缺结点且空缺在右边,即叶子结点只能在层次最大的两层上出现;
2. 对任一结点,如果其右子树的深度为j,则其左子树的深度必为j或j+1。 即度为1的点只有1个或0个
堆分为两种情况,有最大堆和最小堆。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆,在一个摆放好元素的最小堆中,父结点中的元素一定比子结点的元素要小,但对于左右结点的大小则没有规定谁大谁小。
堆常用来实现优先队列,堆的存取是随意的,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。
- 散列表(Hash)
散列表源自于散列函数(Hash function),其思想是如果在结构中存在关键字和T相等的记录,那么必定在F(T)的存储位置可以找到该记录,这样就可以不用进行比较操作而直接取得所查记录。
哈希的时候需要key 通过哈希函数,产生一个唯一的哈希值。也就是f(key) = value. 这个f就是哈希函数。value哈希值
哈希冲突:
有k1 != k2 但是有可能 F(k1) == F(k2),这种情况叫做哈希冲突,处理冲突可以从F,也就是哈希函数进行优化处理。
6.输入一颗二叉树的根结点,求该树的深度?
类型
(1)完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
(3)平衡二叉树——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
二叉树定义:
在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
struct BinaryTreeNode
{
int m_nValue ;
BinaryTreeNode* m_pLeft;
BinarvTreeNode* m_pRight ;
}
- 如果一棵树只有一个结点,它的深度为1。
- 如果根结点只有左子树而没有右子树,那么树的深度应该是其左子树的深度加1;同样如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1。
- 如果既有右子树又有左子树,那该树的深度就是其左、右子树深度的较大值再加1。
int TreeDepth(TreeNode* pRoot)
{
if(pRoot == nullptr)
return 0;
int left = TreeDepth(pRoot->left);
int right = TreeDepth(pRoot->right);
return (left>right) ? (left+1) : (right+1);
}
7.输入一课二叉树的根结点,判断该树是不是平衡二叉树?
- 重复遍历结点
先求出根结点的左右子树的深度,然后判断它们的深度相差不超过1,如果否,则不是一棵二叉树;如果是,再用同样的方法分别判断左子树和右子树是否为平衡二叉树,如果都是,则这就是一棵平衡二叉树。 - 遍历一遍结点
遍历结点的同时记录下该结点的深度,避免重复访问。
方法1:
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
};
int TreeDepth(TreeNode* pRoot){
if(pRoot==NULL)
return 0;
int left=TreeDepth(pRoot->left);
int right=TreeDepth(pRoot->right);
return left>right?(left+1):(right+1);
}
bool IsBalanced(TreeNode* pRoot){
if(pRoot==NULL)
return true;
int left=TreeDepth(pRoot->left);
int right=TreeDepth(pRoot->right);
int diff=left-right;
if(diff>1 || diff<-1)
return false;
return IsBalanced(pRoot->left) && IsBalanced(pRoot->right);
}
方法2:
bool IsBalanced_1(TreeNode* pRoot,int& depth){
if(pRoot==NULL){
depth=0;
return true;
}
int left,right;
int diff;
if(IsBalanced_1(pRoot->left,left) && IsBalanced_1(pRoot->right,right)){
diff=left-right;
if(diff<=1 || diff>=-1){
depth=left>right?left+1:right+1;
return true;
}
}
return false;
}
bool IsBalancedTree(TreeNode* pRoot){
int depth=0;
return IsBalanced_1(pRoot,depth);
}