提示:玉不琢,不成器人不学,不知
其实结构体搞明白了程序我觉得倒是不难的,大家也可以试着分析一下 每一个人都可能有自己的意见
//静态顺序表
typename struct {
ElemType data[MAX];
int size;//实际数据的个数 是用来判断是否达到最大空间
} List;
//动态顺序表
typename struct {
ElmeType* data;
int size;//用来记录表中数据的实际个数方便比较 要不然你如何判断是否达到了capacity?
int capacity;//设置用来记录L的最大空间 若是达到最大空间则需要扩容
}List;
每一个结点不仅要放数据域 还要有一个指针域
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;//这两个是等价的
网上的更多初始化是这样的
typedef struct
{
SElemType *base;
SElemType *top;
int stacksize; //可用最大容量
}SqStack;
ps(若是你也有这样的想法 为什么结构定义成这样 既然是顺序表,不应该一个指针配合一个size或者一个数组吗 如下?)
//静态链表
typedef struct {
ElemType data[MAX];
int length;//数据的实际长度
} SqList;
//动态链表
typedef struct {
ElemType *data=NULL;
//与静态相比这里使用的是指针,不用数组
int length;//表的实际长度
int capacity; //但是同样的这里需要多定义容量(可变化),用来判断是否需要扩容
} SqList;
ps(看网上好像没有人写为什么这样初始化的,我这里就发表一下我个人的愚见)
若是我们将SElemType* top 暂且不看 ,在初始化的时候我们会为他申请空间 ,所以他不就是一个动态顺序表?加上这个top的作用就是定这个顺序表头头在哪,我们方便从栈顶存取数据,所以与就是实际上是可以扩容的
在动态顺序表中我们使用的扩容语句(ElemType *)realloc(*data,size) 但是为什么各种书上都说是不可变的 我想了一个理由就可能是扩容的成本比较大吧 所以说成是不可扩容的
有以上两种链表的结构体的定义方式 我们也可以写出两种定义的方式,不要问对不对 ,应该问题不大,因为实现栈顶基本操作中,只是使用了s.top 最下面两种使用链表定义 一样可以实现
typedef struct {
ElemType data[MAX];
int top;//标志为 只能从top存取元素
//int size; 还需要定义实际数据的长度?这里认为是不需要的,因为top表示的就是数据的实际长度呀
}SqStack;
//动态栈
typedef struct{
ElemType* data;
int top;
//int size;可以不需要size 因为top 就是指向最后的位置
int capacity;//这个时候是需要容量的 方便扩容
}SqStack;
链表中L就是第一个结点的地址 而链栈是限定了只能头部操作的一种链表,而头部一直都被保存L中 所以不需要另外设置一个值 来标记top
// 链栈的存储结构
typedef struct StackNode
{
int data;
struct StackNode *next;
}StackNode,*LinkStack;
限定只能从一端进入一端出去,并且不管是进队列或者是出队列,front和rear都是++ 所以就不会与起始位置重回 所以就需要两个值来标记
//静态顺序表
typename struct {
ElemType data[MAX];
int size;//实际数据的个数 是用来判断是否达到最大空间
}List;
//动态顺序表
typename struct {
ElmeType* data;
int size;//用来记录实际表中元素数目
int capacity;//设置用来记录L的最大空间 若是达到最大空间则需要扩容
}List;
/************基于上面我们可以写出下面****************/
//静态顺序队列
typename struct{
ElemType data[MAX];
//int size;这里也是可以不需要的 因为判满用的的是rear==MAX;
int front;//因为限定了 只能从一端插入一端出 所以相较于
int rear;//
} Queue;
//动态顺序队列
typename struct{
ElemType *data;
//int size;这里也是可以不需要的 因为判满用的的是rear==MAX; 当rear==MAX 我们选择扩容就可以了
int capacity;
int front;
int rear;
} Queue;
那么能不能使用指针而不是使用int 来定义呢?
我认为应该是问题不大的 我写了一篇使用下面定义结构体的顺序队列
typename struct{
ElmeType *data;
int capacity;
ElemType *front;//其实和上面使用int 就取元素的时候是不一样的
ElemTyPe *rear;
} Queue;
所谓循环队列其实也就是在 这个front 与rear 相当于时你追我赶的形式 当他们相遇的时候到底是多走了一圈(队满)还是front 有重新追了上来(队空) 我们只需要在算法中体现(rear+1)==front 就可以了 所以结构体的定义还是不变的 怎么体现循环性 其实简单来说就是坐标的唯一性 当rear 值可能超过MAX的时候 又有空位置 此时一定是数组中的小下标 我们rear%MAX 来使用小下标
//静态顺序队列
typename struct{
ElemType data[MAX];
//int size;这里也是可以不需要的 因为判满用的的是rear==MAX;
int front;//因为限定了 只能从一端插入一端出 所以相较于
int rear;// 其实就是标记数组的下标
} Queue;
链队列 就如他的名字一样既有链表的性,每一个结点不仅存放数据 也有存放下一个结点的指针域,又有队列的性质只能从头出或者从yi巴进入 所以可以想象应该要有两个指针 才方便找头尾 要不然每一次都要遍历 来确定尾部 ,链表的头部插入删除都容易 ,链表的尾部有指针的情况下插入容易,因为删除都需要前一个结点将待删除结点的前一个结点的指针域置为NULL,所以 这里我们使用链表的头作为队列的头,链表的尾 作为队列的尾 还有就是链表需要不需要选择带头结点的呢 这里个人认为是不需要的,因为 我们自在头部插入 尾部删除 这里格式是定下来的 所以也就可以需要头节点(好像有点跑偏了)你写头结点当然也没有问题
typedef struct QNode
{
int data;//数据域,存放数据元素
struct QNode *next;//指针域
}QNode,*QueuePoint;
typedef struct
{
QueuePoint front;//头指针
QueuePoint rear;//尾指针
}LinkQueue;
串的定长顺序存储好像没有什么结构上的要求要求 直接使用静态线性表的结构体定义即可
typename struct {
char data[MAX];
int length;//实际数据的个数 是用来判断是否达到最大空间
} SqString;
就像它的名字一样 这里使用的是动态顺序表的定义就可以了
typedef struct st {
char* ch; //串存放的起始地址
int length; //串的长度
int strsize; //分配的存储空间的大小
}String;
同样的可以不使用最大的存储空间 因为传入的字符数组是可以计算长度的 我们需要多少长度的 就申请多少 长度 就可以将capacity 去除 但是既然可以不使用capacity 你可能有这样的疑惑 这个length 是不是也可以不需要 ?确实是可以,但是你拼接字符串的时候你确定要遍历到’\0‘? 所以定义如下
typedef struct{
char *ch;
int length;
}Hstring;
块链存储,其实就是借用链表的存储结构来存储串。每个节点中可以存储多个单字节。这是普通的链表每个节点存储一个字符
所以可以写出如下结构
//块链存储
typedef struct Chunk{
char ch[3];//这里我定义的是放三个值
struct Chunk *next;
}Chunk;
//定义头尾指针的作用是 方便进行连接操作
//连接的时候别忘了处理第一个串尾的无效字符
typedef struct{
Chunk *head,*tail;//串的头尾指针
int curlen; //串的当前长度 (链表的节点数)
}LString;
正如我们所熟知的 存储方式一般有两种,顺序存储或者链式存储,若是使用顺序存储,则一般二叉树为了能反映二叉树中结点之间的逻辑关系 只能添加并不存在的空结点构造树像完全二叉树一样, 每一个结点与完全二叉树上的结点对应,再存储到一维数组的相应分量中。这样有可能造成空间的极大浪费,所以这里我们使用链式存储 为了方便各种操作,链式存储中也分为了几种方式,这里就不多赘述了 用到一个写一个
我们前面所学的顺序表或者链表统称为线性表 也就是这个结点与下一个结点是一个链接着一个的 但是生活中很多情况下并不是一个接着一个的 比如你家现在两个孩子 等两个孩子在结婚就是两个家庭,两个家庭每一家再有两个孩子 下一代就有四个家庭 为了反应他们之间的关系 ,我们每一个结点中就像普通的链表一样,能找到它的后继才行,但是因为它是二叉树,有两个后继 于是就需要两个指针
于是就有了下面的结构体定义
//二叉树的存储结构,一个数据域,2个指针域
typedef struct BiTNode
{
char data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
基于前面所学知识大家知道 ,一个简单的二叉树虽然也很好 但是资源却没有充分利用 有大量的指针信息被置为空(含有n个结点的二叉链表中 含有n+1 个空链域(n个结点也就意味着有n*2个指针域 根结点不需要指针来指向 也就意味着只有n-1 个结点需要指针来指向 两者相减就是n+1)) 所以人就想 是不是可以利用这些本来存放空结点位置的指针,遍历二叉树就是以一定的规则将二叉树中的结点排列成一个线性序列,从而得到几种遍历序列,使用该序列中的每一个结点(第一个与最后一个除外)都有一个直接前驱和直接后继
所以想到了是不是这些指针可以存放这些直接前驱或者直接后继,但是指针本来是放左孩子或者右孩子的 怎么区分是孩子还是遍历的直接前驱(后继)呢 于是想到了使用一个标记,这个标记代表此时指代的到底是遍历的前驱后继呀 还是左右孩子 使所以还需要一个tag 这里规定当标记值为0时代表该结点有儿子结点,标记值为1时其lchild指针指向上一个结点,rchild指针指向下一个结点。于是我们可以定义出结构体
typedef struct BiThrNode{
struct BiThrNode* Lchild;
int Ltag;
Elemtype data;
int Rtag;
struct BiThrNode* Rchild;
}BiThrNode;
记住我们的操作 需要合并两个树,生成这两个树的父亲结点 所以是从下来往上构建一棵树 所以这里也就需要一个父亲结点 才能从下往上构建一棵树 既然是二叉树 自然也就需要左右孩子,本来我想的也是之前的形式 当然之前的形式是可以求出最优二叉树的,但是这里为什么不使用之前的形式 我也不知道 使用静态的优点在哪也没有看出来
//哈夫曼树结点结构
typedef struct HNode
{
char data; //数据,非叶节点为NULL
double weight;//权重
int parent;//双亲,-1表示没有双亲,即根节点
int lchild;//左孩子,数组下标,-1表示无左孩子,即叶节点
int rchild;//右孩子
}Hnode;
#define MaxInt 32767 //表示极大值∞ 其实就是一种无穷标志
#define MVNum 100 //表示最大顶点数
typedef char VerTexType;//假设顶点的数据结构类型为char
typedef int ArcType;//假设权值类型为整形
typedef struct{
VerTexType vexs[MVNum];//顶点表
ArcType arcs[MVNum][MVNum];//邻接矩阵
int vexnum;//图的当前顶点数
int arcnum;//图的当前边数
}AMGraph;
当一个图为稀疏图,使用邻接矩阵要浪费大量的存储空间 而图的邻接表法结合了顺序存储和链式存储方式,减少了不必要的浪费
结构:对图G中的每一个顶点建立一个单链表,第i 个单链表中结点表示依附于顶点v的边,这个单链表就称为顶点v的边表,边表的头指针和顶点的数据信息采用顺序存储称为顶点表(当然也可以使用单链表来存储),所以在邻接表中存在两种结点,顶点表结点和边表结点 结构如下图
顶点表(称为表头结点如上图的V0,V1)结构分为两部分,数据域和指针域。数据域用于存储顶点数据信息,指针域用于链接下一个节点,跟之前的单链表是一样的
那么除了表头节点我们还有由表头节点引出来的链表,称为边表,若边表的节点存储的是图不是网,那么任仍然可以使用上边图示的存储结构,但是如果存储的是网,那么就可以使用下面的存储结构(adjvex存储与表头节点有关系的顶点的下标,next是连接的链表,info表示的是权重等信息:所以也就需要多带一个信息
若是G为无向图 则需要的存储空间为O(|V|+2|E|) 若是G为有向图,则所需的存储空间为O(|V|+|E|)
对于稀疏图 采用邻接矩阵表示将极大的节省存储空间
在无向图的邻接表中 给定一个顶点 能很快的找到它的所有领边,因为只需要读取它的邻接表即可
**在有向图的邻接表表示中,求一个给定顶点的出度只需要计算其中邻接表中的结点个数 **
求其顶点的出度:
1、遍历整个邻接表中的节点,统计数据域与该顶点所在数组位置下标相同的节点数量,即为该顶点的入度;
2、建立一个逆邻接表,该表中的各顶点链表专门用于存储以此顶点为弧头的所有顶点在数组中的位置下标。
图的邻接表表示并不唯一 因为邻接表表示中,各边表结点的链接次序取决于建立邻接表的算法,以及边的输入次序
#define MVNum 100 //表示最大顶点数
typedef int VerTexType//顶点数据的数据类型
typedef struct ArcNode{//边结构
int adjvex;//该边所指向的顶点的位置
struct ArcNode *nextarc;//指向下一条边的指针
int info; //和边相关的信息如权重等 若是没有权值也可以省略 省略之后就可以于点结构相统一
}ArcNode;
typedef struct VNode{//顶点结构
VerTexType data;
ArcNode *firstarc; //指向第一条依附该顶点的边的指针
}VNode;
//相当于多个链表 将首结点存放于一个数组中 就像单链表中存放长度一样 这里存放的是点与边的个数
typedef struct{//邻接表
ArcNode vertices[MVNum];
int vexnum;//图当前的顶点数
int arcnum;//图当前的边数
};
与邻接表不同,十字链表法仅适用于存储有向图和有向网。不仅如此,十字链表法还改善了邻接表计算图中顶点入度的问题
十字链表存储有向图(网)的方式与邻接表有一些相同,都以图(网)中各顶点为首元节点建立多条链表,同时为了便于管理,还将所有链表的首元节点存储到同一数组(或链表)中。
在十字链表中,对应于有向图中每一个弧有一个结点,对应的每一个顶点也有一个结点 时间复杂度O(|V|+|E|)
从上图 可以看出,首元节点中有一个数据域和两个指针域(分别用 firstin 和 firstout 表示):
firstin 指针用于连接以当前顶点为弧头的其他顶点构成的链表;
firstout 指针用于连接以当前顶点为弧尾的其他顶点构成的链表;
data 用于存储该顶点中的数据;
由此可以看出,十字链表实质上就是为每个顶点建立两个链表,分别存储以该顶点为弧头的所有顶点和以该顶点为弧尾的所有顶点。也可以理解是邻接表于逆邻接表的结合
弧结点与结构:
尾域tailvex:弧尾
头域headvex:弧头
链域hlink:指向弧头相同的下一个弧
链域tlink 指向弧尾相同的下一个弧
info域:指向该弧的相关信息
其实相当于是两个链表,相信若是你第一次看一定是一头雾水 我们来分析一下,首先来看图 中的V0,它是有两个入弧(入度) 分别是 v2和v3 我们来看顶点表中V0中入弧指向的是20 20 的弧头指针指向的是30 也就是对应的 V2 V3 V0的出弧指向的是01 01指向的是02 02 的指向为NULL
或者说一些不算特点的特点:
你看有右边去掉头是不是对应七个 其实也就是七个边, 你连着读一下弧头弧尾的数字,比如 01 指的即是从0 指向1 比如02 就是从0指向2 所以01 弧尾指向的就是 02 因为他们同弧尾,比如01 和31 他们弧头相同 所以01弧头指针指向的就是 31
#define MAX_VERTEX_NUM 20
#define InfoType int//图中弧包含信息的数据类型
#define VertexType int
typedef struct ArcBox{
int tailvex,headvex;//弧尾、弧头对应顶点在数组中的位置下标
struct ArcBox *hlik,*tlink;//分别指向弧头相同和弧尾相同的下一个弧
InfoType *info;//存储弧相关信息的指针
}ArcBox;
typedef struct VexNode{
VertexType data;//顶点的数据域
ArcBox *firstin,*firstout;//指向以该顶点为弧头和弧尾的链表首个结点
}VexNode;
typedef struct {
VexNode xlist[MAX_VERTEX_NUM];//存储顶点的一维数组
int vexnum,arcnum;//记录图的顶点数和弧数
}OLGraph;
若是文章对你的提升由哪怕一点帮助的话 请答应我 不要吝啬你的点赞评论 你的鼓励对作者是一种莫大的鼓励转载请告知哪一部分我检查一下