数据结构
数据结构主要研究非数值计算程序问题中的操作对象以及它们之间的关系,不是研究复杂的算法。
1 导读
该部分主要对数据结构进行学习和总结。该部分内容有些抽象,需要掌握一些基础理论知识(结构体、指针和内存管理)才可以深入理解和剖析。该部分内容主要包括:数据结构的基本知识、线性表、链表、栈、队列和树。
2 数据结构的基本知识
2.1.1 数据结构的概念
数据元素:组成数据的基本单位。
数据对象:性质相同的数据元素的集合(比如:数组、链表)。
数据结构指数据对象中数据元素之间的关系。
2.1.2 数据的逻辑结构及物理关系。
逻辑结构可分为4类:
集合:数据元素间除“同属于一个集合”外,无其它关系。
线性结构:一个对一个,如线性表、数组、栈、队列。
树形结构:一个对多个,如树。
图形结构:多个对多个,如图。
物理结构亦称存储结构,是数据的逻辑结构在计算机存储器内的表示(或映射)。它依赖于计算机。存储结构可分为4大类:顺序、链式、索引、散列。
最常用的存储结构为:
顺序存储结构:借助元素在存储器中的相对位置来表示数据元素间的逻辑关系。
链式存储结构:借助指示元素存储地址的指针表示数据元素间的逻辑关系。
数据结构的操作大致可分为:增删改查。
3 线性表
线性表是由零个或多个数据元素的集合。
线性表中的数据元素之间是有顺序的。
线性表中的数据元素是有限的。
线性表中的数据元素的类型必须相同。
3.1.1 线性表的顺序存储设计
a)线性表的操作:
创建线性表;
销毁线性表;
清空线性表;
将元素插入线性表;
将元素从线性表中删除;
得到线性表中某个位置的元素;
得到线性表的长度。
b) 代码编写流程:
先写代码的原型函数及测试框架。
再在原型代码中编写代码的具体实现。
c)代码的原型函数设计。
该部分主要包含两个部分:一个是数据存储的原型,一个是函数原型。
数据存储的原型(自己思路有其它想法欢迎讨论):
这一部分是点h文件中的数据原型,为了底层使用。
typedef void SeqListNode;
typedef void SeqList;;//定义成void类型的指针表示可以指向任意类型的数据。
这一部分是点c文件中的数据原型,为了底层使用。
//线性表的内部的结构,用于底层的实现
typedef struct _tag_SeqList_
{
unsigned int **data;//用于存储外部传入的地址,内部的节点(也可以是数组,但是设计多大的空间合理呢,这一点需要根据需求设计?)
int length; //线性表的实际长度
int capacity; //线性表的容量
}TSeqLIst;
d)函数原型及设计思路
//创建线性表
//传入参数是希望创建最大的存储空间,返回一个void的*指针,可以指向任意类型。
SeqList *SeqList_Create(int capacity);
//函数实现思路:
创建一个线性表,相当于在堆上分配了一块内存TSeqlList,并根据传入参数对内部数据二级指针分配内容。
为什么要在堆上分配呢?
因为在栈上分配内存当函数结束时就会被释放,不能被外部使用,这一部分是内存管理上学习的(栈、堆的管理及生命周期)。
//销毁线性表
//可以通过判断返回值来判断销毁是否有误,并且传入的参数可以是二级指针,可以在内部进行赋值为为空。
int(void) SeqList_Destroy(SeqList *head);
销毁一个线性表,相当于把创建的线性表释放掉,需要注意有内存才释放而且释放顺序与创建时相反,且释放后需要对指针进行赋值:NULL。
//清空线性表
int SeqList_Clear(SeqList *head);
清空一个线性表,相当于把线性表恢复到创建时的状态(不是销毁哦)。
//将元素插入线性表;
//在线性表的pos位置插入节点node
int SeqList_Insert(SeqList *head, int pos, SeqListNode *node);
向线性表中插入一个元素相当于对线性表中的元素进行赋值。
需要注意:
1)实际长度是否满足最大容量。
2)插入位置是否满足当前长度(冗余纠正)。
3)插入元素相当于把插入点位置和插入点位置之后的元素向后移动,然后再进行插入。
4)插入后不要忘记当前长度加一。
//将元素从线性表中删除;
SeqListNode * SeqList_Del(SeqList *head, int pos);
从线性表中删除元素相当于把某个元素进行剔除。
需要注意:
1)删除位置是否满足当前长度。
2)删除元素相当于先把删除点位置的元素缓存下来,再把删除点位置之后的元素向前移动,最后把缓存下来的元素返回出来。
3)删除后不要忘记当前长度减一。
//得到线性表中某个位置的元素;
SeqListNode * SeqList_Get(SeqList *head, int pos);
需要冗余纠正和有无数据判断。
//得到线性表的长度。
int SeqList_Length(SeqList *head);
//得到线性表的容量。
int SeqList_Capacity(SeqList *head);
上述函数直接返回相应的值即可。
3.2.1 线性表的链式存储设计
线性表的链式存储与顺序存储有一些不同:1、操作中没有得到容量的操作;2、底层设计不同。
a)线性表的操作:
创建线性表;
销毁线性表;
清空线性表;
将元素插入线性表;
将元素从线性表中删除;
得到线性表中某个位置的元素;
得到线性表的长度。
b)底层数据的设计思路。
链表的演变:传统链表想要包含大千世界(自己中包含自己的指针类型);非传统链表想要被大千世界包含((设计一个节点让上层应用包含)偏移量是否为0的两种情况)
数据存储的原型(自己思路有其它想法欢迎讨论):
这一部分是点h文件中的数据原型,为了底层使用。
//底层使用的节点结构,等待其他结构体的包含
typedef struct _tag_LinkListNode_
{
struct _tag_LinkListNode_ *next;
}LinkListNode;
typedef void LinkList;
这一部分是点c文件中的数据原型,为了底层使用。
//线性表的内部的结构,用于底层的实现
//底层使用的节点结构
typedef struct _tag_LinkList_
{
LinkListNode header;
int length;
}TLinkList;
c)函数原型及设计思路
函数的实现与顺序存储基本相同,只不过创建时需要在堆上分配内存,清空和销毁时需要释放内存。插入节点时需要先连接后面的节点再连接当前节点(因为当前节点保存了下一个节点)。删除节点需要先连接缓存要删除的节点,后删除。
for(i = 0; i < pos || Cur->next != NULL; i++)
{
Cur = Cur->next;
}
node->next = Cur->next;
Cur->next = node;
4 链表
该部分主要研究单向循环链表和双向链表。
4.1.1 循环链表
该部分的操作和结构体基本上和单向链表的相似,只不过在数据中有一个游标。
a)线性表的操作:
创建线性表;
销毁线性表;
清空线性表;
将元素插入线性表;
将元素从线性表中删除;
得到线性表中某个位置的元素;
得到线性表的长度。
//新增游标操作
游标复位,将游标重置到第一个节点位置
返回当前位置的游标
游标下移,并返回当前游标位置
删除节点,并返回该节点、将游标下移
数据存储的原型(自己思路有其它想法欢迎讨论):
这一部分是点h文件中的数据原型,为了底层使用。
//底层使用的节点结构,等待其他结构体的包含
typedef struct _tag_CLinkListNode
{
struct _tag_CLinkListNode *next;
}CLinkListNode;
typedef void CLinkList;
这一部分是点c文件中的数据原型,为了底层使用。
//线性表的内部的结构,用于底层的实现
//底层的数据结构
typedef struct _tag_CLinkList_
{
CLinkListNode header;
CLinkListNode *Cursor;
int length;
}TCLinkList;
b)函数原型及设计思路
该部分需要注意插入操作:首次插入需要设置游标位置和首尾相连;首部插入需要将尾部的next指向首部。
删除操作:删除首部时需要将尾部的next指向删除的next;若删除后没有元素了则设置next为NULL;若删除的是游标则重新设置游标位置。
4.1.2 双向链表
该部分的节点数据结构比循环链表的多了一个前项(驱)指针。基本操作与循环链表的相似。
a)线性表的操作:
数据存储的原型(自己思路有其它想法欢迎讨论):
这一部分是点h文件中的数据原型,为了底层使用。
//双向链表的节点结构体,等待其他数据结构包含
typedef struct _tag_DLinkListNode
{
struct _tag_DLinkListNode *next;
struct _tag_DLinkListNode *pre;
}DLinkListNode;
typedef void DLinkList;
这一部分是点c文件中的数据原型,为了底层使用。
//线性表的内部的结构,用于底层的实现
//底层的数据结构
typedef struct _tag_DLinkList_
{
DLinkListNode header;
DLinkListNode *Cursor;
int length;
}TDLinkList;
b)函数原型及设计思路
该部分需要注意插入操作:插入时需要判断是否是头部插入和下一个节点是否为空;首次插入需要设置游标位置。
删除操作:需要判断删除节点的next是否为NULL,并且需要判断是否是头部删除。若删除的是游标则重新设置游标位置。
5 总结
本部分先对线性表的两种存储方式进行了分析和设计,再对链表进行了分析和设计。后面的文章将对栈、队列进行分析和设计。