几种数据存储结构详解
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
正文
所谓数据存储结构,就是数据的元素与元素之间在计算机中的一种表示,它的目的是为了解决空间规模问题,或者是通过空间规模问题从而间接地解决时间规模问题。我们知道,随着输入的数据量越来越大,在有限的内存里,不能把这些数据完全的存下来,这就对数据存储结构和设计存储的算法提出了更高的要求。
本文将介绍几种存储结构,分别为链式结构、树形结构、图结构以及矩阵结构。
第一节 链式存储结构
所谓链式存储结构,一般就是用一个头指针指向链表的第一个节点,如果你要增加新的存储元素时,只需在已有节点的后面插入新结点即可。
链表通常有单链表、双链表、循环链表。在这,我只介绍单链表,双链表和循环链表只是单链表的拓展罢了。下图就是一个简单的单链表图示。
- typedef char DataType; /***假设结点的数据域类型为字符***/
- typedef struct node{ /***结点类型定义***/
- DataType data; /***结点的数据域***/
- struct node *next; /***结点的指针域***/
- }ListNode;
- typedef ListNode *LinkList;
- ListNode *p;
- LinkList head;
- 附注:
- ① LinkList和ListNode *是不同名字的同一个指针类型(命名的不同是为了概念上更明确)
- ② LinkList类型的指针变量head表示它是单链表的头指针
- ③ ListNode *类型的指针变量p表示它是指向某一节点的指针
- typedef char DataType; /***假设结点的数据域类型为字符***/
- typedef struct node{ /***结点类型定义***/
- DataType data; /***结点的数据域***/
- struct node *next; /***结点的指针域***/
- }ListNode;
- typedef ListNode *LinkList;
- ListNode *p;
- LinkList head;
- 附注:
- ① LinkList和ListNode *是不同名字的同一个指针类型(命名的不同是为了概念上更明确)
- ② LinkList类型的指针变量head表示它是单链表的头指针
- ③ ListNode *类型的指针变量p表示它是指向某一节点的指针
typedef char DataType; /***假设结点的数据域类型为字符***/typedef struct node{ /***结点类型定义***/ DataType data; /***结点的数据域***/ struct node *next; /***结点的指针域***/ }ListNode; typedef ListNode *LinkList; ListNode *p; LinkList head;附注: ① LinkList和ListNode *是不同名字的同一个指针类型(命名的不同是为了概念上更明确) ② LinkList类型的指针变量head表示它是单链表的头指针 ③ ListNode *类型的指针变量p表示它是指向某一节点的指针
下面我们来看单链表的操作:创建节点、增加节点、删除节点、查询、修改。
1.创建节点:声明一个节点并为其申请一段内存空间,此节点有数据域和指针域。
- node = (struct List *)malloc(sizeof(struct List));
- node = (struct List *)malloc(sizeof(struct List));
node = (struct List *)malloc(sizeof(struct List));
2.增加节点:插入节点,分为头插入、尾插入和非头尾插入。
①. 在表头插入节点, 如图
- if(p == head) /***其中p为链表中的某一节点***/
- {
- struct list *s = NULL;
- s = (struct list *)malloc(sizeof(struct list)); /***申请空间***/
- s->DataNumber = data; /***为节点s的数据域赋值***/
- /***将节点s插入表头***/
- s->next = p;
- head = s;
- }
- if(p == head) /***其中p为链表中的某一节点***/
- {
- struct list *s = NULL;
- s = (struct list *)malloc(sizeof(struct list)); /***申请空间***/
- s->DataNumber = data; /***为节点s的数据域赋值***/
- /***将节点s插入表头***/
- s->next = p;
- head = s;
- }
if(p == head) /***其中p为链表中的某一节点***/{ struct list *s = NULL; s = (struct list *)malloc(sizeof(struct list)); /***申请空间***/ s->DataNumber = data; /***为节点s的数据域赋值***/ /***将节点s插入表头***/ s->next = p; head = s;}
②. 在表尾插入节点, 如图
- if(p->next == NULL) /***其中p为链表中的某一节点***/
- {
- struct list *s = NULL;
- s = (struct list *)malloc(sizeof(struct list)); /***申请空间***/
- s->DataNumber = data; /***为节点s的数据域赋值***/
- /***将节点s插入表尾***/
- p->next = s;
- s->next = NULL;
- }
- if(p->next == NULL) /***其中p为链表中的某一节点***/
- {
- struct list *s = NULL;
- s = (struct list *)malloc(sizeof(struct list)); /***申请空间***/
- s->DataNumber = data; /***为节点s的数据域赋值***/
- /***将节点s插入表尾***/
- p->next = s;
- s->next = NULL;
- }
if(p->next == NULL) /***其中p为链表中的某一节点***/{ struct list *s = NULL; s = (struct list *)malloc(sizeof(struct list)); /***申请空间***/ s->DataNumber = data; /***为节点s的数据域赋值***/ /***将节点s插入表尾***/ p->next = s; s->next = NULL;}
③. 在表中插入非头尾节点, 如图
- struct list *s = NULL;
- s = (struct list *)malloc(sizeof(struct list)); /***申请空间***/
- s->DataNumber = data; /***为节点s的数据域赋值***/
- /***将节点s插入表中***/
- s->next = p; /***其中p为链表中的某一节点***/
- q->next = s; /***其中q为链表中p节点的前一个节点***/
- struct list *s = NULL;
- s = (struct list *)malloc(sizeof(struct list)); /***申请空间***/
- s->DataNumber = data; /***为节点s的数据域赋值***/
- /***将节点s插入表中***/
- s->next = p; /***其中p为链表中的某一节点***/
- q->next = s; /***其中q为链表中p节点的前一个节点***/
struct list *s = NULL;s = (struct list *)malloc(sizeof(struct list)); /***申请空间***/s->DataNumber = data; /***为节点s的数据域赋值***//***将节点s插入表中***/s->next = p; /***其中p为链表中的某一节点***/q->next = s; /***其中q为链表中p节点的前一个节点***/
3.删除节点:分为删除头结点,删除尾节点,删除头尾节点。
①. 删除表头结点, 如图
- if(p == head) /***p指向链表中的某一节点***/
- {
- head = p->next;
- }
- if(p == head) /***p指向链表中的某一节点***/
- {
- head = p->next;
- }
if(p == head) /***p指向链表中的某一节点***/{ head = p->next;}
②. 删除表尾节点,如图
附注说明:上图中删完尾节点之后,新链表的尾节点下标应为n-1。不过由于作图时只做了尾节点,故用图中的n2节点代替。
删除尾节点的代码如下:
- if(p->next == NULL) /***p指向链表中的某一节点***/
- {
- q->next = NULL; /***q指向链表中的p节点的前一节点**/
- }
- if(p->next == NULL) /***p指向链表中的某一节点***/
- {
- q->next = NULL; /***q指向链表中的p节点的前一节点**/
- }
if(p->next == NULL) /***p指向链表中的某一节点***/{ q->next = NULL; /***q指向链表中的p节点的前一节点**/}
③. 删除非头尾节点,如图
删除非头尾节点的代码如下:
- q->next = p->next; /***p指向链表中的某一节点,q指向链表中的p节点的前一节点***/
- q->next = p->next; /***p指向链表中的某一节点,q指向链表中的p节点的前一节点***/
q->next = p->next; /***p指向链表中的某一节点,q指向链表中的p节点的前一节点***/
4.查询节点:在链表中找到你想要找的那个节点。此操作是根据数据域的内容来完成的。查询只能从表头开始,当要找的节点的数据域内容与当前不相符时,只需让当前节点指向下一结点即可,如此这样,直到找到那个节点。
附注:此操作就不在这用图和代码说明了。
5.修改节点:修改某个节点数据域的内容。首先查询到这个节点,然后对这个节点数据域的内容进行修改。
附注:同上
ok,链表的几种操作介绍完了,接下来我们来总结一下链表的几个特点。
链式存储结构的特点:
1.易插入,易删除。不用移动节点,只需改变节点中指针的指向。
2.查询速度慢:每进行一次查询,都要从表头开始,速度慢,效率低。
扩展阅读
链表:http://public.whut.edu.cn/comptsci/web/data/512.htm
第二节 树形存储结构
所谓树形存储结构,就是数据元素与元素之间存在着一对多关系的数据结构。在树形存储结构中,树的根节点没有前驱结点,其余的每个节点有且只有一个前驱结点,除叶子结点没有后续节点外,其他节点的后续节点可以有一个或者多个。
如下图就是一棵简单的树形结构:
说到树形结构,我们最先想到的就是二叉树。我们常常利用二叉树这种结构来解决一些算法方面的问题,比如堆排序、二分检索等。所以在树形结构这节我只重点详解二叉树结构。那么二叉树到底是怎样的呢?如下图就是一颗简单的二叉树:
附注:有关树的概念以及一些性质在此不做解释,有意者请到百科一览。
二叉树的类型描述如下:
- typedef struct tree
- {
- char data;
- struct tree * lchild, * rchild; /***左右孩子指针***/
- }tree;
- typedef struct tree
- {
- char data;
- struct tree * lchild, * rchild; /***左右孩子指针***/
- }tree;
typedef struct tree{ char data; struct tree * lchild, * rchild; /***左右孩子指针***/}tree;
二叉树的操作:创建节二叉树,创建节点,遍历二叉树,求二叉树的深度。
1.创建二叉树:声明一棵树并为其申请存储空间。
- struct tree * T = NULL;
- T = (struct tree *)malloc(sizeof(struct tree));
- struct tree * T = NULL;
- T = (struct tree *)malloc(sizeof(struct tree));
struct tree * T = NULL;T = (struct tree *)malloc(sizeof(struct tree));
2.创建节点:除根节点之外,二叉树的节点有左右节点之分。
创建节点的代码如下:
- struct tree * createTree()
- {
- char NodeData;
- scanf(" %c", &NodeData);
- if(NodeData == '#')
- return NULL;
- else
- {
- struct tree * T = NULL;
- T = (struct tree *)malloc(sizeof(struct tree));
- T->data = NodeData;
- T->lchild = createTree();
- T->rchild = createTree();
- return T;
- }
- }
- struct tree * createTree()
- {
- char NodeData;
- scanf(" %c", &NodeData);
- if(NodeData == '#')
- return NULL;
- else
- {
- struct tree * T = NULL;
- T = (struct tree *)malloc(sizeof(struct tree));
- T->data = NodeData;
- T->lchild = createTree();
- T->rchild = createTree();
- return T;
- }
- }
struct tree * createTree(){ char NodeData; scanf(" %c", &NodeData); if(NodeData == '#') return NULL; else { struct tree * T = NULL; T = (struct tree *)malloc(sizeof(struct tree)); T->data = NodeData; T->lchild = createTree(); T->rchild = createTree(); return T; }}
3.遍历二叉树:分为先序遍历、中序遍历、后续遍历。
①.先序遍历:若二叉树非空,则依次执行如下操作:
(1) 访问根结点;
(2) 遍历左子树;
(3) 遍历右子树。
如图:
先序遍历的代码如下:
- void PreTravser(struct tree * T)
- {
- if(T == NULL)
- return;
- else
- {
- printf("%c",T->data);
- PreTravser(T->lchild);
- PreTravser(T->rchild);
- }
- }
- void PreTravser(struct tree * T)
- {
- if(T == NULL)
- return;
- else
- {
- printf("%c",T->data);
- PreTravser(T->lchild);
- PreTravser(T->rchild);
- }
- }
void PreTravser(struct tree * T){ if(T == NULL) return; else { printf("%c",T->data); PreTravser(T->lchild); PreTravser(T->rchild); }}
②.中序遍历:若二叉树非空,则依次执行如下操作:
(1)遍历左子树;
(2)访问根结点;
(3)遍历右子树。
如图:
中序遍历的代码如下:
- void MidTravser(struct tree * T)
- {
- if(!T)
- {
- return;
- }
- else
- {
- MidTravser(T->lchild);
- printf("%c",T->data);
- MidTravser(T->rchild);
- }
- }
- void MidTravser(struct tree * T)
- {
- if(!T)
- {
- return;
- }
- else
- {
- MidTravser(T->lchild);
- printf("%c",T->data);
- MidTravser(T->rchild);
- }
- }
void MidTravser(struct tree * T){ if(!T) { return; } else { MidTravser(T->lchild); printf("%c",T->data); MidTravser(T->rchild); }}
③.后续遍历:若二叉树非空,则依次执行如下操作:
(1)遍历左子树;
(2)遍历右子树;
(3)访问根结点。
如图:
- void PostTravser(struct tree * T)
- {
- if(!T)
- return;
- else
- {
- PostTravser(T->lchild);
- PostTravser(T->rchild);
- printf("%c->",T->data);
- }
- }
- void PostTravser(struct tree * T)
- {
- if(!T)
- return;
- else
- {
- PostTravser(T->lchild);
- PostTravser(T->rchild);
- printf("%c->",T->data);
- }
- }
void PostTravser(struct tree * T){ if(!T) return; else { PostTravser(T->lchild); PostTravser(T->rchild); printf("%c->",T->data); }}
4.求二叉树的深度:树中所有结点层次的最大值,也称高度。
二叉树的深度表示如下图:
- int treeDeepth(struct tree * T)
- {
- int i, j;
- if(!T)
- return 0;
- else
- {
- if(T->lchild)
- i = treeDeepth(T->lchild);
- else
- i = 0;
- if(T->rchild)
- j = treeDeepth(T->rchild);
- else
- j = 0;
- }
- return i > j? i+1:j+1;
- }
- int treeDeepth(struct tree * T)
- {
- int i, j;
- if(!T)
- return 0;
- else
- {
- if(T->lchild)
- i = treeDeepth(T->lchild);
- else
- i = 0;
- if(T->rchild)
- j = treeDeepth(T->rchild);
- else
- j = 0;
- }
- return i > j? i+1:j+1;
- }
int treeDeepth(struct tree * T){ int i, j; if(!T) return 0; else { if(T->lchild) i = treeDeepth(T->lchild); else i = 0; if(T->rchild) j = treeDeepth(T->rchild); else j = 0; } return i > j? i+1:j+1; }
好了,二叉树的几种操作介绍完了。
拓展阅读
二叉树:http://student.zjzk.cn/course_ware/data_structure/web/DOWNLOAD/%CA%FD%BE%DD%BD%E1%B9%B9%D3%EB%CB%E3%B7%A82.htm
赫夫曼编码:http://blog.csdn.net/fengchaokobe/article/details/6969217
第三节 图型存储结构
所谓图形结构,就是数据元素与元素之间的关系是任意的,任意两个元素之间均可相关,即每个节点可能有多个前驱结点和多个后继结点,因此图形结构的存储一般是采用链接的方式。图分为有向图和无向图两种结构,如下图
1.图的结构有好几种,在实际应用中需根据具体的情况选择合适的结点结构和表结构。常用的有数组结构、邻接表。
①.数组结构
数组结构的类型描述如下:
- typedef char VertexType; /***顶点类型***/
- typedef int EdgeType; /***边权值类型***/
- #define maxvex 100 /***顶点的最大个数***/
- typedef struct
- {
- VertexType vexs[maxvex]; /***顶点个数***/
- EdgeType arc[maxvex][maxvex]; /***两顶点构成边的权值***/
- }Mgraph;
- typedef char VertexType; /***顶点类型***/
- typedef int EdgeType; /***边权值类型***/
- #define maxvex 100 /***顶点的最大个数***/
- typedef struct
- {
- VertexType vexs[maxvex]; /***顶点个数***/
- EdgeType arc[maxvex][maxvex]; /***两顶点构成边的权值***/
- }Mgraph;
typedef char VertexType; /***顶点类型***/typedef int EdgeType; /***边权值类型***/#define maxvex 100 /***顶点的最大个数***/typedef struct{ VertexType vexs[maxvex]; /***顶点个数***/ EdgeType arc[maxvex][maxvex]; /***两顶点构成边的权值***/}Mgraph;附注:当前图为无向图时,图中某两个顶点VA和VB构成一条边时,其权值可表示为EdgeType arc[VA][VB];当前图为有向图时,图中某两个顶点VA和VB构成一条边时,并且是由VA指向VB,其权值可表示为EdgeType arc[VA][VB],如果是由VB指向VA,其权值可表示为EdgeType arc[VB][VA]。
②.邻接表
邻接表的类型描述如下:
- typedef char VertexType; // 顶点类型
- typedef int EdgeType; //边权值类型
- typedef struct EdgeNode //边表节点
- {
- int adjvex; //邻接点域,存储该顶点对应的下标
- EdgeType weight; //用于存储权值
- struct EdgeNode *next; //链域,指向下一个邻接点
- }EdgeNode;
- typedef struct VertexNode //顶点表节点
- {
- VertexType data; //顶点域,存储顶点信息
- EdgeNode * firstedge; //边表头指针
- }VertexNode,AdjList[MAXVEX];
- typedef struct
- {
- AdjList adjList;
- int numVertexes,numEdges; //图当前顶点数和边数
- }GraphAdjList;
- typedef char VertexType; // 顶点类型
- typedef int EdgeType; //边权值类型
- typedef struct EdgeNode //边表节点
- {
- int adjvex; //邻接点域,存储该顶点对应的下标
- EdgeType weight; //用于存储权值
- struct EdgeNode *next; //链域,指向下一个邻接点
- }EdgeNode;
- typedef struct VertexNode //顶点表节点
- {
- VertexType data; //顶点域,存储顶点信息
- EdgeNode * firstedge; //边表头指针
- }VertexNode,AdjList[MAXVEX];
- typedef struct
- {
- AdjList adjList;
- int numVertexes,numEdges; //图当前顶点数和边数
- }GraphAdjList;
typedef char VertexType; // 顶点类型typedef int EdgeType; //边权值类型typedef struct EdgeNode //边表节点{ int adjvex; //邻接点域,存储该顶点对应的下标 EdgeType weight; //用于存储权值 struct EdgeNode *next; //链域,指向下一个邻接点}EdgeNode;typedef struct VertexNode //顶点表节点{ VertexType data; //顶点域,存储顶点信息 EdgeNode * firstedge; //边表头指针}VertexNode,AdjList[MAXVEX];typedef struct{ AdjList adjList; int numVertexes,numEdges; //图当前顶点数和边数}GraphAdjList;
2.图的遍历:从图中的某一节点出发访问图中的其余节点,且使每一节点仅被访问一次。图的遍历算法是求解图的连通性问题、拓扑排序和求路径等算法的基础。图的遍历分为深度优先遍历和广度优先遍历,且它们对无向图和有向图均适用。
①. 深度优先遍历
定义说明:假设给定图G的初态是所有顶点均未曾访问过。在G中任选一顶点V为初始出发点,则深度优先遍历可定义如下:首先访问出发点V,并将其标记为已访问过;然后依次从V出发搜索v的每个邻接点W。若W未曾访问过,则以W为新的出发点继续进行深度优先遍历,直至图中所有和源点V有路径相通的顶点(亦称为从源点可达的顶点)均已被访问为止。若此时图中仍有未访问的顶点,则另选一个尚未访问的顶点作为新的源点重复上述过程,直至图中所有顶点均已被访问为止。
深度遍历过程如下图:
②. 广度优先遍历
定义说明:假设从图中某顶点V出发,在访问了V之后一次访问V的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直至图中所有已被访问的顶点的邻接点都被访问到。若此时图中还有顶点未被访问,则另选图中一个未曾被访问的顶点作为起始点,重复上述过程,直至图中所有顶点都被访问到为止。换句话说,广度优先遍历图的过程是以V为起点,由近至远,依次访问和V有路径相同且路径长度为1,2,...的顶点。
广度遍历过程如下图:
扩展阅读
最小生成树:Prim算法,Kruskal算法
最短路径:Dijkstra算法,Floyd算法
第四节 结束语
想想,写写,画画......
原文地址: http://blog.csdn.net/fengchaokobe/article/details/7416547
给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
几种数据存储结构详解相关推荐
- 零零散散学算法之详解几种数据存储结构
影响空间规模的几种数据存储结构 正文 所谓数据存储结构,就是数据的元素与元素之间在计算机中的一种表示,它的目的是为了解决空间规模问题,或者是通过空间规模问题从而间接地解决时间规模问题.我们知道,随着输 ...
- 数据结构图,图存储结构详解
1. 数据结构的图存储结构 我们知道,数据之间的关系有 3 种,分别是 "一对一"."一对多" 和 "多对多",前两种关系的数据可分别用线性 ...
- mysql数据库存储引擎和索引的描述_Mysql InnoDB引擎的索引与存储结构详解
前言 在Oracle 和SQL Server等数据库中只有一种存储引擎,所有数据存储管理机制都是一样的. 而MySql数据库提供了多种存储引擎.用户可以根据不同的需求为数据表选择不同的存储引擎,用户也 ...
- TCP/IP数据包结构详解
一般来说,网络编程我们只需要调用一些封装好的函数或者组件就能完成大部分的工作,但是一些特殊的情况下,就需要深入的理解 网络数据包的结构,以及协议分析.如:网络监控,故障排查等-- IP包是不安全的,但 ...
- 【计算机网络 24】TCP/IP数据包结构详解
一.前言 一般来说,网络编程我们只需要调用一些封装好的函数或者组件就能完成大部分的工作,但是一些特殊的情况下,就需要深入的理解 网络数据包的结构,以及协议分析.如:网络监控,故障排查等. IP包是不安 ...
- IP数据报、TCP数据包结构详解
[关键词] TCP IP 数据包 结构 具体解释 网络 协议 一般来说,网络编程我们仅仅须要调用一些封装好的函数或者组件就能完毕大部分的工作,可是一些特殊的情况下,就须要深入的理解 网络数据包的结构, ...
- 将数据库服务器的文件D 改名为,MySQL如何更改数据库数据存储目录详解
前言 MySQL数据库默认的数据库文件位于/var/lib/mysql下,有时候由于存储规划等原因,需要更改MySQL数据库的数据存储目录.下文总结整理了实践过程的操作步骤.话不多说了,一起来看看吧 ...
- mysql 邻接表_图的邻接表存储结构详解
通常,图更多的是采用链表存储,具体的存储方法有 3 种,分别是邻接表.邻接多重表和十字链表. 本节先讲解图的邻接表存储法.邻接表既适用于存储无向图,也适用于存储有向图. 在具体讲解邻接表存储图的实现方 ...
- JVM(运行时数据区结构)详解一
JVM(Java虚拟机) JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功 ...
最新文章
- 【特征匹配】ORB原理与源码解析
- Linux下使用perf进行性能分析,并导出火焰图
- jinchuang正文 nginx配置limit_conn_zone来限制并发连接数以及下载带宽
- android app功能 配置,配置安装时分发 | Android 开发者 | Android Developers
- 在Unix/Linux上令(java)JVM支持中文输出
- 浅谈python_浅谈Python(二)
- IntelliJ IDEA 运行 Maven 项目
- 腾讯视频客户端导出MP4格式
- jws 方式表格导出,excel文件导出,rest风格接口实现
- QT每日一练day23:鼠标进入与离开事件
- Oracle的回收站和闪回查询机制(二)
- 如何修改apache 2最大连接数
- GPS数据格式的分析与处理
- 数字化转型实践:世界级 2B 数字化营销的方法框架
- 语言缩写c-a,各国语言缩写及语言代码查询
- 关于j2sdk的设置
- 分布式系统的阿喀琉斯之踵:数据一致性!
- 万字案例 | 用Python建立客户流失预测模型(含源数据+代码)
- 线性代数 抽象非齐次方程解的问题
- 天网系统服务器码,天网管理系统
热门文章
- connectionTimeout和CommandTimeout
- 面试官:说说你对版本管理的理解?常用的版本管理工具有哪些?
- Edition-Based Redefinition白皮书笔记
- Android开发--Zxing库实现二维码/条形码扫描识别
- python文本挖掘与分析:热剧《延禧攻略》用户评论分析
- Python实现多个视频合成视频的功能你知道吗
- selenium 自动化测试面试题及答案
- ValidateRequest=quot;falsequot; 无效
- docker部署项目流程-基于若依系统(vue+springboot)
- 应用程序启动初始化失败 0xc0150002,请点击确定关闭应用程序