基础数据类型
前言
@
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
0x12345678在内存中的存储形式
内存地址 | 小端模式存放内容 | 大端模式存放内容 |
---|---|---|
0x4000 | 0x78 | 0x12 |
0x4001 | 0x56 | 0x34 |
0x4002 | 0x34 | 0x56 |
0x4003 | 0x12 | 0x78 |
线性表
定义:线性表是具有相同数据类型的$n(n>=0)$个数据元素的有限序列
顺序表
定义:线性表的顺序存储又称顺序表。
它是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理上位置也是相邻的。
特点及用途:
- 数据密度大:由于线性表中的数据元素在物理上连续的内存中保存。所以数据密度大
- 遍历算法无特别点:与兄弟链表的全遍历都是从第一个结点开始往下查找,时间复杂度均为$O(n)$
- 对某个元素的相邻元素存取较方便。根据指针的+-1性质即可存取相邻结点的信息。
- 插入删除操作较复杂:在顺序表的非尾结点中插入或删除信息时都要把其后的结点向后或者向前移动一位,代价大。但是在尾结点中执行插入删除操作时无差别。
链表
定义:线性表的链式存储又称单链表,它是指通过一组任意的存储单元来存储线性表中数据元素。
特点及用途:
- 数据密度小 :链表的数据在内存中是以分散的形式存储的。
- 遍历算法无特别点:同与顺序表的比较。
- 插 入删除操作较简单:插入删除时仅相关结点修改指针地址即可,勿须移动结点 。
- 插入操作分头插法和尾插法。
- 尾插法:$s\rightarrow{}next=p\rightarrow{}next;p\rightarrow{}next=s;$(p为链表中最后一个结点,s是待插入结点。)
- 头插法:$s\rightarrow{}next=L\rightarrow{}next;l\rightarrow{}next=s;$(s为链表的头结点,其不保存数据,s是待插入结点 ).
双链表
双链表即双向链表,在数据元素结构体中加了前驱指针。
插入、删除操作中注意把两个指针都进行修改就完事了。哈哈
循环单链表
与单链表的区别仅为:表中的最后一个结点不是指向NULL,而是指向第一个结点,从而形成一个环。
链表判空条件:头结点指针是否为头结点。
循环双链表
与循环单链表的区别在于,在数据元素的结构体中增加前驱指针。
链表判空条件:头结点的prior指针和next指针均为L。
顺序表与链表的比较
-
存取方式
顺序表可以随机存取,也可以顺序存取,链表仅能从表头顺序存取元素。 -
逻辑结构与物理结构
-
查找、插入、删除操作
按值查找时,顺序表无序时,二者时间复杂度均为$O(N)$;顺序表有序时,可采用折半查找。因此时间复杂度为$O(nlog_2n)$
按序号查找,顺序表的随机访问,时间复杂度为$O(1)$,链表为:$O(N)$. -
空间分配
顺序表在存储满的时候,若加入新的元素,会内存溢出,而链表不会。
update in 19:25 2019/7/2
栈
定义:只允许在一端进行插入或者删除操作的线性表。
栈首先是一个线性表,但是仅限在一端进行插入删除操作。
顺序栈
#define Maxsize 50
typedef struct{
Elemtype data[Maxsize];
int top;
}SqStack;
栈空条件:$S.top-1$
栈满条件:$S.topMaxsize-1$
栈长:$S.top+1$
栈的基本操作:
初始化 $s.top-1$
判栈空 $return s.top-1$
进栈 $s.data[S.top--]$
出栈 $s.data[s.top--];$
读栈顶元素 $s.data[s.top];$
默认栈顶指针初始化s.top=-1,但若栈顶指针初始化s.top=0时,细节相应修改。
共享栈:
栈空:top0=-1 0号栈为空,top1=-1 1号栈为空
栈满:top0-top1=1时栈满。
进栈:对应栈顶指针修改(加1 或减1)
只有在栈被存储满时才会发生上溢出
链式栈
typedef struct Linknode{
ElemType data;
sturct Linknode *next;
}*LiStack;
注:与顺序表的操作大致相同。
队列
定义:队列也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。
队列的顺序存储结构
- 队列的顺序存储
#define Maxsize 50
typedef struct{
Elemtype data[Maxsize];
int front,rear;
}SqQueue;
初始状态(队空条件):$Q.frontQ.rear0$
进队操作:队不满时,先送到值到队尾元素,再将队尾指针加1
出队操作:队不空时,先取队头元素,再将队头指针加1
$Q.rearMaxsize$不为队满条件。因为可以出现“上溢出”——即$Q.rearMaxsize$,但是$Q.front!=0$
- 循环队列
可解决顺序存储结构的“上溢出”的缺点。
初始时:$Q.front=Q.rear=0$
队首指针进1:$Q.front=(Q.front+1)%Maxsize$
队尾指针进1:$Q.rear=(Q.rear+1)%Maxsize$
出队时:队首指针进1(顺序针)
入队时:队首指针进1(顺序针)
区分队空与队满的三种处理方式
- 牺牲一个单元来区分即队满:$(Q.rear+1)%Maxsize==Q.front$
- 类型中增设表示元素个数的数据成员即队满:$Q.size==Maxsize$
- 类型中增设tag数据成员,以区分队满还是队空,tag=0时队空。tag=1时队满。
队列的链式存储结构
typedef struct{
ElemType data;
struct LinkNode *Next;
}LinkNode;
typedef struct{
LinkNode *front,*rear;
}LinkQueue;
不带头结点队空:$Q.frontNULL&&Q.realNULL$
带头结点队空:$Q.front==Q.real$
update in 15:57 2019/7/3
双端队列
入队和出队可以在两端或者一端
-
栈在表达式中的应用
-
栈在括号匹配中的应用
-
栈在递归中的应用
-
队列在遍历中的应用
树与二叉树
一些基本定义:
度:树中的一个结点的子结点的个数称为该结点的度。树中结点的最大度。数称为树的度
结点的深度是从根结点开始自顶向下累加的。
结点的高度是从叶结点开始自底向上累加的。树的高度为最大高度。
树中的结点数等于所有的度数加1.
二叉树的概念
几种的特殊的二叉树:
-
满二叉树:一棵高度为h,且含有$2^h-1$个结点的二叉树,即每层都含有最多的结点。
-
完全二叉树:设一个高度为$h$,有$n$个结点的二叉树,当且仅当其每个结点都与高度为h的满二叉树中的编号为$1-n$的结点一一对应时,称为完全二叉树。
-
二叉排序树:一棵树或者是空二叉树,或者是具有如下性质的二叉树:左子树上的所有结点的关键字均小于根结点的关键字;而右子树反之。左子树和右子树同时又是一个二叉排序树。
-
平衡二叉树:树上任一结点的左子树和右子树的深度之差不超过一
二叉树的一些基本的性质:
-
非空二叉树的叶子结点数等于度为2的结点数加1.即$n_0=n_2+1$
总结点数为$n_0+n_1+n_2$.且分支(度)总数:$n_1+2_2$
-
具有$n$个$(n\gt{}0$结点的完全二叉树的高度为$\ulcorner{}log_2(n+1)\urcorner$或$\llcorner{}log_2n\lrcorner+1$
二叉树的遍历与线索二叉树
二叉树的遍历
先序遍历
根左右
void PreOrder(BiTreee T){
if(T!=NULL){
visit(T);
PreOrder(T->lchild);
PreOrder(T->Rchild);
}
}
中序遍历
左根右
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild);
visit(T);
InOrder(T->rchild);
}
}
后序遍历
左右中
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild);
InOrder(T->rchild);
visit(T);
}
}
递归算法和非递归算法的转换(有待思考)
遍历的算法可以用栈来改写成非递归程序,下面是非递归的中序遍历算法:
void InOrder2(BiTree T){
//二叉树中序遍历的非递归算法,需要借助一个栈
InitStack(S);BiTree p=T; //初始化栈;p是遍历指针。
while(p||!IsEmpty(S)){ //栈不为空或p不空时循环
if(p){ //根指针进栈,遍历左子树
Push(S,p); //每遇到非空二叉树先向左走
p=p->lchild;
}else{ //根指针退栈,访问根结点,遍历右子树
Pop(S,P);visit(p); //退栈,访问根结点
p=p->rchild; //再向右子树走
}
}
}
层次遍历
层次遍历是从最根结点开始将每层的结点从左向右访问完后再访问下一层,其具体的算法可以借助队列来实现:
void LevelOrder(BiTree T){
InitQueue(Q);
BiTree p;
enQueue(Q,T);
while(!IsEmpty(Q)){
DeQueue(Q,p);
visit(p);
if(P->lchild!=NULL){
enQueue(Q,p->lchild);
}
if(p->rchild!=NULL){
enQueue(Q,p->rchild);
}
}
}
由遍历序列构造二叉树
tips are listed below.
- 先序遍历序列中,第一个结点一定是二叉树的根结点。
- 中序遍历中,根结点必然奖中序序分割成两个子序列,前者为左子树的中序序列,后者为右子树的中序序列
- 后序序列的最后一个结点同先序序列的第一个结点可以将中序序列分割成两个子序列。
线索二叉树
线索二叉树的存储结构如下:
typedef struct ThreadNode{
ElemType data;
struct ThreadNode *lchild,*rchild;
int ltag,rtag;
}
$$
ltag=\left{
\begin{aligned}
0,&lchild域指示结点的左孩子\
1,&lchild域指示结点的前驱
\end{aligned}
\right.\
rtag=\left{
\begin{aligned}
0,&rchild域指示结点的右孩子\
1,&rchild域指示结点的后继
\end{aligned}
\right.
$$
中序遍历对二叉树线索线的递归算法如下:
void InThread(ThreadTree &p,ThreadTree &pre){
//中序遍历对二叉树线索化的递归算法
if(p!=NULL){
InThread(p->lchild,pre); //递归,线索化左子树
if(p->lchild==NULL){ //左子树为空,建立前驱线索
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL){//无直接后继,线索化其
pre->rchild=p; //建立前驱结点的后继线索
pre->rtag=1;
}
pre=p; //标记当前结点成为刚刚访问的结点
InThread(p->rchild,pre); //递归,线索化右子树
}
}
通过中序遍历建立中序线索二叉树的主过程算法如下:
void CreateInThread(ThreadTree T){
ThreadTree pre=NULL;
if(T!=NULL){ //非空二叉树,线索化
InThread(T,pre); //线索化二叉树
pre->rchild=NULL; //处理遍历的最后一个结点
pre->rtag=1;
}
}
update in 17:58 2019/7/8
树、森林
树的存储结构
-
双亲表示法
#define MAX_TREE_SIZE 100 //树中的最多结点 typedef struct{ //树的结点定义 ElemType data; //数据元素 int parent; //双亲位置域 }PTNODE; typedef struct{ //树的类型定义 PTNODE nodes[MAX_TREE_SIZE];//双亲表示 int n; //结点数 }PTREE;
较易得到每个结点的双亲结点,但是寻找子结点需要遍历整个结构。
-
孩子表示法
//链表中孩子结点表示 typedef struct CHNode{ int pos; //孩子的位置 CHNode *next; //指向下一个孩子的指针 }CHNode; //数组中双亲结点的表示 typedef struct CHNode1{ int data; //数据元素 CHNode *firchild; //指向第一个孩子的指针。 }CHNode1; //树的类型表示 typedef struct { CHNode1 nodes[MAXsize]; //所有的结点 int n; //结点的个数 }CHTree;
寻找子女的操作十分直接,但寻找双亲的操作需要遍历n个结点中的孩子链表指针域所指向 的n个孩子链表。
-
孩子兄弟表示法
typedef struct CSNode{ int data; //结点的数据 CSNode *firstchild,*nextbling;//第一个孩子和下一个兄弟。 }
存储起来比较灵活,优点是可以方便地实现树转换为二叉树,易于查找结点的孩子等,但是缺点是从当前结点查找其双新结点比较麻烦。若为每个结点增设一个parent域指向其父结点,则查找结点的父结点较方便。
树、森林与二叉树的转换
树转换为二叉树:
每个结点左指针指向它的第一个孩子结点,右指针指向它在树中的相邻兄弟结点,可表示为“左孩子右兄弟”。由于根结点没有兄弟,所以由树转换而得的二叉树没有右子树。
二叉树转换森林:
先将森林中的每棵树转换为二叉树,再将第一棵树的根转换后的二叉树的根,将第一棵树的左子树根的左子树,将第二棵树作为转换后的右子树。将第三棵树作为 二叉树的右子树,以此类推。
update 2019年7月8日20:51