1、数据结构
数据结构:是指计算机存储和组织数据的一种方式,相互之间存在一种和多种特定关系的数据集合。
- 数据:指客观事物的符号表示,能够输入到计算机并且能够被计算机所识别处理的符号。
- 结构:现实世界中,一些数据存在一定的隐含关系,隐含关系称之为 '结构' 。
- 运算:对存在关系的数据能够进行处理。(增、删、改、查)
1.1、逻辑结构
描述数据与数据之间的关系。
a、集合结构:数据与数据之间除了属于同一个集合,再无其他关系。
b、线性结构:数据与数据之间呈现一对一线性关系。(元素前后都只有一个元素)
c、树形结构:数据与数据之间呈现一对多的关系。(层次关系 / 从属关系,元素前只有一个元素,元素后可以有多个元素)
d、图形结构:数据与数据之间呈现多对多关系。(数据与数据之间都可能存在关系,元素前后都可以有多个元素)
1.2、存储结构
描述数据的存储,同时存储关系。
a、顺序存储:在计算机中使用一块连续的内存空间存放所有元素。
连续空间的地址用于表示线性数据的关系,如果元素有前驱,就存储当前位置的前一个地址中,如果元素有后继就存储在后一个地址中。
b、链式存储(离散存储):每个元素的存储位置不一定连续不能通过地址的先后顺序来表示关系。
链式存储中,每个元素存储数据本身之外,还要存储下一个数据的地址,从而表示数据于数据之间的关系。
1.3、数据运算
能够对数据进行增、删、改、查操作。
1.4、线性表
线性表:每一个数据元素只有唯一的前驱,唯一的后继,第一个没有前驱,最后一个没有后继,这种数据的关系表示就叫做线性表(线性结构)。
有零个或多个数据元素的有限序列。
逻辑结构为线性结构,存储结构为顺序存储叫做顺序表。
顺序表开辟的内存空间是连续的,通过地址来表示数据元素之间的关系(在线性表中是什么位置,存储到相应发地址中,相当于就是连续空间的中下标(地址))
- 顺序表特点:
1、内存中存储的位置于关系一致。
- 顺序表的操作
1、创建顺序表
顺序表的表示:
组成:1.连续的空间(数组、malloc())、2.当前顺序表的元素个数、3.整个连续空间的大小
struct 顺序表表名
{
元素类型 data[元素个数]; // 顺序表元素要存储的值
int num; // 顺序表元素个数
int size; //顺序表大小
};
1.5、链表
链表:逻辑结构为线性关系,存储结构为链式存储(离散存储),每个元素都要存储下一个元素的地址,可以构成线性关系存储表示。
链表数据元素组成:1.数据元素值(存储数据元素本身)、2.下一个元素的地址(关系)。
链表的操作:
1、创建链表
struct Node
{
数据域:数据类型 变量名;
指针域:指向类型 * next;
};
- 指向类型:struct Node。
头结点:在链表中额外添加一个用于表示整个链表的结点,在整个链表开始位置(表头),头结点不存储任何数据,只存储了第一个元素结点的地址(头指针),方便进行操作链表。
头指针:是一个指针,指向链表中第一个结点。
链表的元素是动态调整,需要时才创建一个新结点。
1.5.1、链表分类
- 单向链表:前一个元素只存储后一个元素的地址,方向是单向的,最后一个结点为空指针。
- 双向链表:结点中有两个指针,一个用于存储前一个结点的地址,一个用于后一个结点的地址。
- 循环链表:
- 单向循环链表:是一个单向链表,但在最后一个结点不是存储空指针,而是存储头结点地址。
- 作用:可以从任意一个结点位置遍历单链表的所有结点。
- 单向循环链表:是一个单向链表,但在最后一个结点不是存储空指针,而是存储头结点地址。
创建:
在创建头结点时,next指针存储头结点的位置。
struct Node * head = malloc(sizeof(struct Node));
head->next = head;
判断单向循环链表结束:
当前结点地址为p,头节点地址为head;判断p == head,则说明是最后一个链表。
1.6、栈与队列
特殊线性表:栈与队列。限制在线性表中操作的位置。
1.6.1、栈
栈:是线性表,逻辑结构是线性结构,在有线性关系的数据中,限制只能在线性表的一端进行操作(插入、删除),另一端不允许进行操作。----- 先进后出。
能够进行插入、删除的位置为线性表的一个端点位置,称为栈顶;另一端称为栈底;插入操作称为入栈 / 压栈;删除操作称为出栈 / 弹栈。
1.6.1.1、顺序栈
逻辑结构:栈(特殊线性表)。
存储结构:顺序存储。
使用一段连续空间进行存储。
为了方便操作,设定栈底不变,进行插入删除操作栈顶改变。
- 顺序栈表示:
struct 栈名
{
数据类型 数组名[大小];
int size;(栈大小)
int top;(栈顶位置)
}
1.6.1.2、链式栈
逻辑结构:栈。(先进后出的线性关系)
存储结构:链式存储。
链式栈:使用链式方式(存储数据且存储地址)存储栈元素,且对数据元素的操作只能在一端栈顶进行(插入、删除)。
链式栈是通过链表来实现,每入栈一个元素,向链表中添加一个结点(在栈顶),每出栈一个元素。从链表中删除一个结点(在栈顶)。
因为栈具有“先进后出”的特点,在链表尾进行插入删除,要遍历整个链表来找到链表尾;而在头进行插入删除,直接就可以根据头结点找到首元素结点。
使用链表的“头插法”,每一个元素存储上一个元素的地址。
1.6.2、队列
队列是一种特殊的线性表,只允许在表的一端进行插入操作,而在表的另一端只能进行删除操作;允许删除的一端叫队头,删除操作叫做出队;允许插入的一端叫队尾,插入操作叫做入队。
在队列中,可以使用队头指针标记可以删除的位置,也叫做队头位置。
在队列中,可以使用队尾指针标记可以插入的位置,也叫做队尾位置。
队列:先进先出,先入队的元素先出队。
- 链式队列
逻辑结构:线性表,队列。
存储结构:链式存储。
使用链表头作为队列头进行删除;使用链表尾作为队尾进行插入。
队头指针存储头节点指针方便删除;队尾指针存储当前最后元素结点地址,方便插入。
结点:
struct Node
{
data;
struct Node * next;
}
指针存储:
struct
{
struct Node * front;//队头指针
struct Node * rear;//队尾指针
}
- 顺序队列
逻辑结构:队列。
存储结构:顺序存储。
在出队和入队操作时,只需要移动队头、队尾位置,表示插入删除操作;在存储开始时,让队头队尾指针指向同一个位置。
顺序存储时,队头队尾指针存储即将操作的位置。
队头、队尾指针在队列结束位置时,下一次操作回到队列开始位置,便实现队列循环。
队头、队尾指针移动构成循环:front = (front + 1) % size,rear = (rear + 1) % size。
判断队列为空:队头指针等于队尾指针(front == rear)。(说明两个指针指向同一个位置)
判断队列为满:由于完全满与空的条件一致,所以把差一个存满作为存满的条件,队尾+1等于队头((rear + 1) % 10 ==front)。
循环队列
struct queue
{
数据类型 data[size]; //连续空间
int size; //大小
int front; //队头
int rear; //队尾
}
1.7、树与二叉树
-
树型关系
是指数据与数据之间呈现一对多的关系(层次结构)
1.7.1、树的概念
树:由n个结点构成的有限集合,结点与结点之间的关系呈现树形关系。
有且仅有一个特定结点称为根的结构。
其余结点可分为m个互不相交的有限集合,每个集合又是一个树,叫做根的子树。
- 结点的度:是指结点拥有子树的个数(拥有分支的数目)。
- 树的度:指数中结点度的最大值称为数的度。
- 叶子结点(终端结点):度为0的结点,称为叶子结点。
- 孩子结点(子结点):结点的子树的根节点,称为该结点的子结点。
- 双亲结点(父结点):结点的根结点称为该子结点的父结点。
- 兄弟结点:同一个父节点的子结点称为兄弟结点。
- 树的深度(高度):树中结点的最大层次(层数),称为树的深度。
- 有序树和无序树
- 有序树:如果把树中结点的子树,看做是从左往右有次序的(即不能交换),则称为该树为有序树。
- 无序树::如果把树中结点的子树,看做是无序的,则称为该树为无序树。
- 森林:多颗互不相交的树的集合。
1.7.2、树的存储
顺序存储
用一段连续空间存储多个结点,同时表示关系。
链式存储
每个结点单独表示,存储自己的元素值及每个子结点的地址。
1.7.3、二叉树
二叉树:在树中,每个结点至多只有两颗子树(树的度
性质
1、在二叉树中第k层上最多有2^(k-1)个结点
2、在深度为k的二叉树中,最多有2^k-1个结点
3、对于任意二叉树,如果叶子结点树为n0,度为2的结点树为n2,存在:n0 = n2 + 1。
满二叉树:一颗深度为k且有2^k - 1个结点的二叉树为满二叉树。(二叉树中每一个结点都有左右子树)
完全二叉树:深度为k,在k - 1层之前都是满二叉树,最后一层从左往右叶子节点都是相邻。
- 二叉树的存储
顺序存储:
根节点从1开始存储:左子结点存储在2*i处,右子结点存储在2*i + 1处;i为父节点存储的位置。父节点位置:子结点存储位置 / 2;
根节点从0开始存储:左子结点存储在2 * i + 1处,右子结点存储在2 * (i + 1)处;i为父节点存储的位置。父节点位置:(子结点存储位置 + 1) / 2 - 1;
链式存储:
即存储了数据,又存储了左右子结点的地址。
- 二叉树的遍历
是指沿着某个方向顺序搜索二叉树,对树中的每一个结点访问且仅访问一次。
1、先序遍历:是指访问时,先访问二叉树的根节点,再访问二叉树的左子结点,最后访问二叉树的右子结点。(根----左----右)
2、中序遍历:是指访问时,先访问二叉树的左子节点,再访问二叉树的根结点,最后访问二叉树的右子结点。(左----根----右)
3、后序遍历:是指访问时,先访问二叉树的左子节点,再访问二叉树的右子结点,最后访问二叉树的根结点。(左----右----根)
4、层次遍历:从第一层开始依次访问每一层的所有结点。
1.7.3.1、哈夫曼树
带权路径长度:路径长度 * 权值
权值:二叉树中的结点存放了权重值
路径长度:从根节点到当前结点所经过的分支数目。
树的带权路径长度:是指所有二叉树所有叶子结点的带权路径长度之和。
哈夫曼树:带权路径长度之后最小的二叉树。
1.8、图
- 图:有两个集合,一个用于存储1图中的元素,叫做顶点集合,另一个用于存储图中的关系,叫做边集合。
- 无向边:顶点之间的关系是相互的。
- 有向边:顶点之间的关系是单向的,也叫做弧。
- 有向图:图之间顶点的关系是相互的图。
- 无向图:图之间顶点的关系是单向的图。
- 稠密图:在图中顶点的关系比较多的图。
- 稀疏图:在图中顶点的关系比较少的图。
- 连通图:任意两点之间有路径可以到达的图。
路径:两个顶点之间存在一条可以到达的关系。
连通分量:在图中的部分顶点之间是连通的,也叫做联通子图。
极大连通子图:在连通子图外,额外添加任意图中一点,不能构成连通,则是极大连通子图。
- 网:带权的图叫做网。
1.8.1、图的存储
邻接矩阵:所谓邻接矩阵存储结构就每个顶点用一个一维数组存储边的信息,这样所有点合起来就是用矩阵表示图中各顶点之间的邻接关系。所谓矩阵其实就是二维数组。
邻接表:邻接表是用来表示有限图的无序列表的集合,每个列表描述了图中相邻顶点的集合。
2、算法
算法:对解决实际问题的步骤描述。
- 评判算法优劣:
1、消耗的时间
算法执行的时间越少,算法越好。
时间复杂度:算法中,基本操作重复执行的次数(频度)之和(所有语句的语句频度之和)。通常用T(n)表示,n叫做问题规模。
语句频度:在算法中,语句的执行次数。
大O表示法:随在问题规模n的增大,算法执行时间的增长率和T(n)的增长率相同,称为算法的渐进时间复杂度。
n趋近无穷大,得到的时间复杂度表示,只留下最高次幂,常数级就是O(1)。
//例:
for(int i = 0; i < 100; i++)
for(int j = 0; j < 200; i++)
printf("%d\n", i + j);
//时间复杂度T(n) = 1 + n + n + n + n^2 + n^2 + n^2 = 3n^2 + 3n + 1 ----- 大O表示法:O(n^2)
2、消耗的空间
算法执行的占用内存空间越少,算法越好。
空间复杂度:衡量内存资源占用的情况。当程序执行时,所占的内存资源的大小。用S(n)表示,随着问题规模n的变化而变化。
2.1、查找
2.1.1、直接查找
待查找的数据依次与数据元素进行比较。
2.2.2、折半查找
使用折半查找法,必须要求数据元素有序。
设置端点查找范围
mid = (low + high) / 2;
mid就是当前中间位置。比较查找数据与中间值(升序,降序则相反):相等,则查找成功;x < mid:x只会落在mid的左边 high = mid - 1;x > mid:x只会落在mid的右边 low = mid + 1;没找到数据:high < low。
2.2、排序
2.2.1、直接选择排序
直接选择排序(选择排序):从数据中,找到最小的数据,直接与排序这个位置进行交换,直接选择最小的数据进行交换。
for(int j = 0; j < 9; j++){
int min = j;
for(int i = j + 1; i < 10; i++){
if(a[min] > a[i])
min = i;
}
int mid = a[j];
a[j] = a[min];
a[min] = mid;
}
2.2.2、冒泡排序
冒泡排序(交换排序):在整个元素中,把相邻的元素进行比较,并将最大的元素放在最后。
for(int i = 0; i < 9; i++){
for(int j = 0; j < 9 - i; j++){
if(a[j] > a[j + 1]){
int mid = a[j];
a[j] = a[j + 1];
a[j + 1] =mid;
}
}
}
总结
数据结构是计算机科学中非常重要的一门基础课程,它研究了如何组织和存储数据,以便高效地进行操作和访问。在我学习数据结构的过程中,我深深体会到了它的重要性和实用性。以下是我对数据结构学习的一些总结。
-
数据结构的基本概念和分类:
数据结构可以分为线性结构和非线性结构。其中线性结构包括数组、链表、栈和队列,而非线性结构包括树和图。每种数据结构都有其独特的特点和应用场景,我们需要根据实际需求选择合适的结构。 -
算法与数据结构的关系:
数据结构和算法是紧密相关的,数据结构提供了存储和组织数据的方式,而算法则是对数据进行操作和处理的方法。良好的数据结构可以提高算法的效率,而高效的算法可以更好地利用数据结构的特性。 -
常见的数据结构操作:
数据结构提供了一系列的操作,如插入、删除、查找等。对于不同的数据结构,这些操作的时间复杂度也会有所不同。例如,数组的插入和删除操作的时间复杂度为O(n),而链表的插入和删除操作的时间复杂度为O(1)。 -
数据结构的存储方式:
数据结构可以采用不同的存储方式,常见的方式有顺序存储和链式存储。顺序存储使用数组来存储数据,可以根据下标直接访问元素,而链式存储使用指针来连接各个节点,灵活性更强。 -
递归与数据结构:
递归是一种常见的解决问题的方法,它在数据结构中也有广泛的应用。例如,在树的遍历中,可以使用递归来实现前序、中序和后序遍历。 -
动态数据结构:
动态数据结构是指在运行过程中可以动态地申请和释放内存的数据结构,主要包括链表、树和图等。相对于静态数据结构,动态数据结构更加灵活,但同时也要注意内存管理。
通过学习数据结构,我不仅掌握了各种常见的数据结构和它们的操作,还理解了算法和数据结构之间的关系。数据结构的学习不仅仅是为了应对考试,更是为了提高我解决实际问题的能力。无论是在面试中,还是在实际的开发中,对数据结构的深入理解都能为我提供更多的解决方案和优化的空间。数据结构是计算机科学中的基础,我会不断地进行学习和实践,深化对数据结构的理解和运用能力。