VisuAlgo!
一,Date Structure的核心技术是分解和抽象
二,基本概念和常用术语
①Date是对信息的一种符号表示,包括数字,文档,记录,数组,句子,单词,算式都成为数据。在计算机中,任何能输入到计算机中的信息都叫做数据。
——Date Object是众多具有相同性质的Date Element的集合
②Date Element是Data的基本单位,在计算机科学中作为整体进行考虑和处理。
③Date Item是Date Element的最小不可分割单位。
④Date Structure是具有一种或多种关系的数据元素的集合,按某种逻辑关系组织起来的一批数据,按照一定的存储方式存储在计算机存储器中,在其之上定义一种运算的集合,就成为一个Date Structure,计算机在存储数据时,不仅要存储数据本身,还要存储数据之间相互的逻辑结构。存储方式成为物理结构或存储结构,在逻辑结构上存储“做什么”,在存储结构上规定数据“如何做”。
三,逻辑结构
1,逻辑结构通常分为四大类:
集合(无关系)
线性结构(唯一前驱和后继)
非线性结构:树形结构(唯一前驱,多后继),图形结构(多前驱多后继)
例如
D={1,2,3,4,5,6} //数据元素的集合
R={r} //对应关系的集合
r={(1,2),(2,3),(3,4),(4,5),(5,6)} //序偶的集合
——关系r是序偶的集合,如名字,有顺序的两个结点,称为序偶:
①数据元素的集合+数据关系的集合
②每个数据元素成为一个结点
③前一结点称为后一结点的前驱,反之,则称为后继
四,存储结构
1,计算机的存储结构是由有限个存储单元组成,每个单元有唯一的地址,地址连续编码,多个则称为存储区域
2,4种基本存储方法
①顺序存储:把逻辑上相邻的元素存储在物理位置相邻的位置,逻辑关系由存储单元的邻接关系来体现。
②链式存储:将逻辑相邻的元素不存储在物理位置相邻的位置,而是把所占用的存储单元分为两部分,一部分存储数据元素本身,另一部分存储结点后继元素的地址。
③索引存储:存储节点信息的同时,建立索引表,索引表的每一项包含关键字和地址,关键字能唯一识别数据元素的数据项,针对数据内容的存储公式
④散列存储:以数据结构的关键字为自变量,通过散列函数来定向计算该元素的存储位置,针对数据内容的存储方式
五,运算集合
1,每种逻辑结构都有一种运算的集合
①引用型运算:不改变原有element的状态,只进行读取
②加工型运算:改变原有element的状态,如内容,个数等。
六,抽象数据类型
1,数据类型
①类型显示或隐式的规定了在程序执行期间变量的一切可能取值和一切在这些值上可能进行的操作,数据类型就是一组值的集合加上在这些值上进行的操作的集合之和。
②数据结构分两类,一类是基本类型,是给定的,值不可分解,如int,double;一类是结构类型,其可分解,其值也可分解,如结构体,数组etc.
③一般数据类型又称为预定义数据类型
2,抽象数据类型
①是一个数学模型以及在它上面定义的一组操作(数据的逻辑结构和其上定义的操作)
②通常由用户自定义其逻辑结构与操作说明,不考虑结构和操作的具体实现
③特点:使用和实现分离开(类型的定义和其实现分离开)
七,线性表
1,逻辑结构
①L = (a1,a2,a3……an-1,an)
a.数据元素个数n称为表的长度,n=0时称为空表
b.非空线性表中,存在唯一一个只有后继没前驱的表头元素,同理,职业前驱没有后继的称为表尾元素
c.除表头元素有且仅有一个前驱,除表尾元素有且仅有一个后继
d.表中元素数据类型相同,n的取值个数有限
②操作
a.初始化,构造一个新表
b.求线性表的长度
c.按序号取元素
d.按值查找元素,返回第一个结点的位置,若没找到,返回一个值代表没有找到
e.插入
f.删除
g.清空
2,存储结构
①顺序存储结构
//简单直接,所占空间连续,随机存取任一结点按物理位置相邻来实现其逻辑结构,叫做顺序表
a.定义,在一个结构体中定义一个数组存贮数据元素,用一个length存储表长
// #define MaxLen 100;
typedef int DataType;
typedef struct
{ DataType data[MaxLen];
int length;
}SeqList;
b.初始化,令L->length = 0;即可
c.求表长,return L->length;即可
d.按位置取结点值,若查找位置小于1或者大于MaxLen返回错误,若范围正确return data[i - 1];即可
e.按值取结点位置,用一个i=0来初始化,用一组循环来比较第data[i]个元素的值是否为所需值,若不是i++若是return i;即是所求值结点所在位置。
f.插入,将每个数据元素依次后移,然后插入新节点,将表长+1
//void InsertList(SsqList * L, DataType e,int i)
{//错误检测
for(int j = L -> length - 1; j >= i - 1;j --)
L- > data[j+1] = L -> data[j];
L -> data[i-1] = e;
L -> length ++;
}
//注意当空间已满,或插入位置非法时,不可进行插入操作
g.删除,先去除欲删除节点,再将i+1……n的元素依次移动到i……n-1
②链式存储结构
散乱,结点次序与物理顺序不一定一致,由于第一个结点没有前驱,要用一个head来存储头结点的地址
L=(head,a1,a2,....an)
——应用malloc申请结点变量地址,用free函数来释放一个结点变量地址
a.单链表:
(1).建立set,一个结构体内放一个存放数据内容的变量和一个存放下一个结构体的指针。
——头插法:在head与a1间插入新结点
——尾插法:在an后插入新结点
(2).查找Locate,单链表中不会像顺序表中那样直接按序号找元素,只能从头指针开始,逐一循环。
——按序查:……
——按值查:……
(3).插入Inser,插入到第i个结点的位置,步骤
one.找到第ai-1个结点的位置
two.创建一个新结点 s
three.新结点指针指向ai
four.令q指向新结点。
——例如:
ListNode *p, q,s;
int j = 1; p = head;
if(i<1||i>GetLength(head)+1)exit(1);
s=(ListNode )malloc(sizeof(ListNode));
s->data = x;
while(j <= i)
{ q=p;p=p->next;
j++; }
s->next = q->next;
q->next = s;
(4)删除delete,要删除结点i,首先找到i-1个结点,用一个指针指向i,然后把i-1个结点的指针指向i+1,最后free指向i的指针
b.循环链表,将表中终端结点的指针域指向表头
(1)没有NULL指针,终止条件不是p或者p->next是否为空,而是判断他们是否等于某一特定指针
(2)从任意一结点开始,不止能访问其后继结点,能访问所有结点。
c.双链表,结点结构体中,增加一项指针域prior,用于存储上一结点的位置,插入和删除必须同时修改两个方向上的指针。
d.静态链表……
e.应用,一元多项式的加法
3线性表总结
①顺序表与链表,若较为稳定则使用顺序表,若需要频繁的插入或删除操作,则用链表
②线性表初期建表容易,无额外开销,可以随机访问任一结点,缺点是建表初期需要预估大小,插入删除操作相应繁琐,需要频繁的移动数组元素。
③链表建表相应复杂,额外开销未知,不能随机访问任一结点,但内存管理完美,执行删除插入操作复杂度小。
④由于高级语言中没有指针类型,可以定义一个结构体数组,为数组的每个元素附加一个链接指针,从而形成静态链表。
八,堆栈
1.定义
①限定仅在表的一端进行插入删除的线性表,通常称插入删除的一端称为栈顶,另一端称为栈底,特点:先进后出,后进先出,故也称为后进先出表。
2.基本运算
①初始化
②压栈
③出栈
④判断空栈
⑤取栈顶元素
3.顺序存储结构(顺序栈)
#define Maxsize <存储数据元素的最大个数>
typedef <栈中元素实际数据类型> SElemType;
typedef struct {
Selemtype data[Maxsize];
int top;
}STACK;
STACK *s;
——习惯上将top = -1表示栈空,top = Maxsize - 1表示栈满,常称此处的top为“栈顶指针”,但其实并不是指针类型变量。
①初始化,top=-1.
②压栈,首先判断是否栈满,再通过s->top+1后s->data[s->top]来完成压栈
③出栈,判断是否为空栈,再先把s->data[s->top]赋值给指针变量后,后s->top-1
④判断空栈,判断s->top是否为-1
⑤取栈顶元素,判断是否为空栈,再先s->data[s->top]赋值给数据类型变量,后s->top-1
4.链式存储结构(链栈)
typedef <栈中元素实际数据类型> SElemType;
typedef struct Snode{
SElemtype data;
struct Snode next;
}LinkSTACK;
LinkSTACK top;
①初始化,malloc函数为top分配内存,再做内存分配成功判断,若成功top->next=NULL;
②压栈,为一个s分配内存空间,判断是否成功,若成功,头插法插入链表结点
③出栈,判断是否为空栈,若不是,用一个s存储栈顶元素内容,随后删除链表结点,释放s的内存地址。
④判断空栈,判断s->next是否为NULL
⑤取栈顶元素,判断是否为空栈,再先s->data[s->top]赋值给数据类型变量,后s->top-1
5.递归
①使用递归的三种情况
(1).很多函数是递归定义的
(2).有些数据结构本身固有递归特定,可以利用递归来定义
(3).使用递归化解更简单
②两个限制条件
(1).规模较大的原问题能分解成一个或多个规模较小但具有类似于原问题特性的问题。
(2).存在一个或多个无需分解即可直接求解的最小子问题,经过有限次递归后,子问题的规模减至最小。
九,队列
1,定义
仅限在队尾进行插入操作,在对头进行删除操作的“先进先出”表。
①初始化
②入队
③出队
④判断空队
⑤取栈顶元素
2,顺序存储结构
typedef 实际元素数据类型 QElemtype;
typedef struct{
QElemType data[Maxsize];
int front,rear;
}QUEUE;
——顺序队结构体中数组低下标一端为队头,高下标一端为队尾
——fornt总是指向对头元素的前一位置,rear总是指向队尾元素
——假溢出现象用循环队列来处理
①初始化 : Q->front = Q->rear = 0;
②入队:先判断是否空队,然后Q->rear = (Q->rear+1)%Maxsize; 对rear指针指向地方赋值即可
③出队:先判断是否空队,然后Q->rear = (Q->rear+1)%Maxsize; 用指针指向front指针指向地方,然后释放即可。
④判断空队 :判断是否Q->front = Q->rear;
⑤取队头元素:*x = (Q -> data[Q->front+1]) % Maxsize
⑥计算队列成员个数:(rear - front +Maxsize)%Maxsize
3,链式存储结构
typedef 实际元素数据类型 QElemType;
typedef struct qnode{
QElemType data;
struct qnode *next;
}QTYPE;
typedef struct {
QTYPE * front, *rear;
}LinkQUEUE;
LinkQUEUE LQ;
——同时带有头指针和尾指针的单链表
①初始化:新建结点p,申请空间,判断内存,p->next = NULL; LQ->front = LQ ->rear = p;
②入队:新建结点s,申请空间,判断内存,s->data = x; s->next = NULL; LQ->rear->next = s; LQ ->rear = s;
③出队:
void OutQueue(LinkQUEUE * LQ, QElemType *x)
{
QTYPE *p;
if(EmptyQueue(LQ))
{printf("cuowu");
return 0;}
p = LQ->front->next;
*x = p->data;
LQ->front->next = p->next;
if(LQ->front->next == NULL)
LQ->rear = LQ->front;
free(p);
}
④判断空队:LQ->front == LQ ->rear?1:0;
⑤取队头元素:*x = LQ->front->next->data;
九,串
1,定义
又称作字符串,形如S = “a1a2a3a4an-1”
①所含字符个数称为串的长度,主串中任意连续字符组成的子序列称为串的子串
②字符串以'\0'结束,所以串的实际长度要多1个。串中每个字符占1个字节
2,顺序存储结构
#define MaxLen <最大串长>
typedef struct string{
char str[MaxLen];
int curlen;
};
①判断Equal,先比较长度是否一样,再比较每一个字符是否一样
②链接concat,先计算两个字符串的长度和,将t复制到s后面。
3,链式存储结构
typedef struct Lstring{
char data;
struct Lstring * next;
}Lstring;
①判断Equal,无视长度,同上
②链接concat,无视长度,同上
④求子串Lsubstr,先进行错误判断,找到指定的i位置,开始向后赋值n位给一个新的指针,返回
4,优缺点,即链式存储和顺序存储的老毛病。
5,应用,
1,文本编辑器
2,KMP判子串算法
十,数组
1,定义
①数据元素固定,不可后添加删除。
②元素数据类型相同
③每个数据元素值有唯一下标对应
④随机存储结构,随机存取任意元素
⑤数据元素是一个数据结构,可以是线性表中的一个元素,也可以是一个线性表
——下标个数又称为数组的维度
2,顺序存储结构
①一维数组按下标顺序分配即可
②多维数组:
(1):行优先,a00,a01,a02,a10,a11……
(2):列优先,a10,a20,a30,a11,a21……
3,应用:
①矩阵的压缩存储
——若aij==aji,则称Anxn为矩阵
(1)对称矩阵
——关于对角线对称,这样我们就只需要存储上半角或者下半角的元素,就节约了一半
(2)三角矩阵
——下三角或者上三角全部为c(常数)或者0,c或0只用一个数组元素来存储
(3)对角矩阵
——对角线附近有元素,其余元素全是0
(4)稀疏矩阵(非零元素远大于0元素个数)
——使用三元组(横坐标,纵坐标,值),运用数组行优先顺序来保存为线性表
——三元组结构体:
typedef struct {
int row;
int col;
elementtype val;
}Triple;
——三元组顺序表:
typedef struct {
int m;
int n;
int len;//非零元素总数
Triple data[Maxsize];
}TMatrix M;
①初始化
M->m = 0;M->n = 0;M->len = 0;
②创建一个稀疏矩阵
传入稀疏矩阵的行,列值。赋值M->mM->n,输入行列值,传入M->data[k].row/col/val,每一次增加k的值,最后传入M->k.
③输出稀硫矩阵
……
④求转置矩阵
先将M中的m,n,len赋值给t,再将每一个m里面的row col val赋值给t
⑤矩阵相加
先判断行数,优先加行数小的,再判断列数,优先加列数小的,若行列数都相等,则直接赋值data的三维坐标,val为n+m的val值总和。最后将mn的剩余数据按序赋值到t。
例如:void MatrixAdd(TMatrix* M , TMatrix* N, TMatrix* T)
{int i = 0,j = 0, k = 0;
elementtype v;
if(M->m!=N->m||M->n!=N->n)
{printf("矩阵不匹配");
exit(1)};
T->m = M->m;T->n = M->n;
while(ilen&&jlen)
{
if(M->data[i].rowdata[j].row)
{
T->data[k].row=M->data[i].row;
T->data[k].col=M->data[i].col;
T->data[k].val=M->data[i].val;
k++;
i++;
}
else if(M->data[i].row > N->data[j].row)
{
T->data[k].row=N->data[i].row;
T->data[k].col=N->data[i].col;
T->data[k].val=N->data[i].val;
k++;
j++;
}
else//行相等
{
if(M->data[i].col < N->data[j].col)
{
T->data[k].row=M->data[i].row;
T->data[k].col=M->data[i].col;
T->data[k].val=M->data[i].val;
k++;
i++;
}
else if(M->data[i].row > N->data[j].row)
{
T->data[k].row=N->data[i].row;
T->data[k].col=N->data[i].col;
T->data[k].val=N->data[i].val;
k++;
j++;
}
else//行列都相等
{
v = M->data[i].val + N->data[j].val;
T->data[k].row=M->data[i].row;
T->data[k].col=M->data[i].col;
T->data[k].val = v;
}
i++;
j++;
}
}
for(;ilen;i++)
{
T->data[k].row = M->data[i].row;
T->data[k].col = M->data[i].col;
T->data[k].val = M->data[i].val;
k++;
}
for(;ilen;i++)
{
T->data[k].row = M->data[j].row;
T->data[k].col = M->data[j].col;
T->data[k].val = M->data[j].val;
k++;
}
T->len = k;
}
——————难点是行号的匹配问题,要进行行号的匹配
十,树
1,基本术语
重要的概念:
①度,拥有的子树个数
②叶子结点(分支结点):(非)终端节点的意思
③森林:互不相交的树的集合
④深度:层次数
表示方法:树形图,文氏图,广义表,凹入表示法
2,二叉树
——每个节点只有两个子树左子树,右子树,而且有序!左右子树互换位置是两种二叉树
性质①二叉树的第i层上有2^(i - 1)
性质②深度为k的二叉树有2^k-1个结点
性质③叶子结点=结点数+1
两种形态:
①满二叉树,即所有结点都有左子树和右子树
②完全二叉树,左子树有,右子树不一定有,左子树没有,右子树一定没有。
(1)有n个结点的完全二叉树深度为【log2n】+1
(2)
顺序存储结构
typedef int TElemType;
typedef TElemType SqBiTree【最大容纳数】
SqBiTree bt;
按从左到右从上到下给完全二叉树编号存入数组,没有子树的地方用0来表示。
链式存储结构
typedef int TElemType;
typedef struct node {
TElemType data;
struct node * lchild, rchild;
}BiTnode, BiTree;
(1)二叉链表存储结构 lchild data rchild 三个结点域
(2)三叉链表存储结构 lchild data parent rchild 四个结点域
(3)线索链表
遍历
(1)先序遍历(DLR)
1,递归实现
void x(BiTree bt) {
if(bt) {
visit(bt->data);
x(bt->lchild);
x(bt->rchild);
}
}
2,非递归实现
void x(BiTree bt){
Bitree stack【Maxsize】;//定义顺序栈
int top = -1;
BiTree p = bt;
while(p||top!=-1) {
while(p) {
visit(p->data);//访问结点数据
if(栈没满) {
top++; stack【top】 = p }
p = p->lchild;
}
if(top!=-1){p=stack[top]; top--; p = p->rchild;}
}
(2)中序遍历(LDR),改变visit函数的位置
(3)后序遍历(LRD),改变xisit函数的位置
——便利操作是非线性结构线性化
——访问根结点和遍历其左右子树的顺序不同,每一次遍历每个结点需要经过三次,“先后序”遍历只是在这三次的那一次访问结点元素的区别
(4)层次遍历
利用顺序队列数据结构
二,二叉树的线索化。
三,树和森林
1,双亲表示法, typedef char TElemType;
typedef struct PTnode {
TElemType data;
int parent;
}PTNode;
typedef struct {
PTNode node【】;
int r,n;
}
2,孩子表示法,typedef struct CTNode {
int child;
struct CTNode * next;
} * ChildPtr;
typedef struct CTNode {
TElemType data;
ChildPtr FirstChild;
}CTBox;
typedef struct {
CTBox nodes【】;
int r, n;
}CTree;
3,也可以综合以上两种制造双亲孩子链表,不做解释
4,兄弟孩子表示法
(与二叉树的表示方法差不多,顾已成为二叉树表示法)
typedef char TElemType;
typedef struct CSNode {
TElemType data;
struct CSNode *firstchild * nextsibling;
}CSNode, * CSTree;
三,
1,树与二叉树的转换
1,因为树的兄弟孩子表示法,顾给定一棵树,可以找到唯一的一颗二叉树与之对应
1,树转换为二叉树
①凡是下一个兄弟,用线连起来(加线);
②去掉每个结点除第一个孩子之外与孩子的连线(抹线)
③排列(调整)
2,二叉树转化为树
①若结点是双亲的左孩子,则把该结点的右结点,右结点的右结…都与双亲连起来(加线)
②抹去所有双亲结点与右孩子的连线(抹线)
③排列(调整)
——这样转换得到二叉树和普通树唯一对应
2,森林与二叉树的转换
1,森林转换为二叉树
将森林中的每一棵树转为二叉树(转换),将每一个二叉树的根节点连线(加线),然后调整
2,二叉树转换为森林
抹去二叉树根节点右链与其所有右链上的右结点连线(抹线),将二叉树转换为树(转换),调整
3,树和森林的遍历
四,二叉树的应用
最后,图
一 重要定义。
1.弧或边带权的图称为有向网或无向网
2.如果无向图中任意一对顶点都是连通的,就成为连通图
3.如果有向图中,对于每一对顶点vi和vj都存再一条vi到vj的路径以及vj到vi的路径,就成为强连通图
4.度表示和顶点关联的边的数目,有向图存在出度和入度,顾名思义
5.路径上经过边的数目称为路径长度,除顶点和终点可以相同外,其余各点不同称为简单路径,若起点和终点相同称为环或回路。
二 存储结构。
1.邻接矩阵
(1)无向图的邻接矩阵通常是对称的,有向则不是,因为(vi,vj)与(vj,vi)的不同
(2)便于查找任意边或边上的权
——查找图中是否有边(vi,vj)查找第i行第j列上的元素是否为有效数
(3)便于查找任一顶点的度
——对无向图,vi的度是第i行或第i列上的有效元素个数
——对有向图,vi的出度就是第i行的有效元素的个数,入度就是第i列上的有效元素个数
typedef int V;
typedef struct {
int A【】【】//邻接矩阵存储顶点间的邻接关系
V v【】//存储顶点的表
int vex,arc//图的顶点数和弧数
}MGraph;
(4)缺点!!:存储空间的浪费,无论有多少条边总是需要开销nXn空间
2.邻接表
(1)保存有向图中,把保存出边的叫邻接表,保存入边的叫逆邻接表,需要占用
n+e个存储空间
(2)找一个顶点的边或者邻接点,从顶点表中取出对应的表头指针,从表头指针开始查找即可
typedef int V;
typedef struct ArcNode{//定义边表结点
int adjvex;//该弧指向顶点的位置
struct ArcNode * nextarc;//指向下一条弧的指针
int info;//该弧上的权值
}ArcNode;
typedef struct VNode {
Vertex data;
ArcNode *firstarc; //指向边表
}VNode;
typedef struct {
VNode adjlist【】;
int vexnum, arcnum//顶点数,弧数
}
3.邻接多重表
顶点表还是由data值域存放的顶点加上firstedge指针域存放的
4.十字链表
三 图的遍历
1.深度优先搜索
从一个顶点开始,先访问一个顶点的邻接顶点,依次下去,直到没有没访问的邻接顶点的邻接顶点,就倒回去,直到有没被访问的邻接顶点的那个节点再访问其没被访问的邻接顶点
(1)采用visited数组表示顶点是否已经被访问
(2)采用邻接表的存储方式表示图,整个算法分DFS和DFST
2.广度优先搜索
从一个顶点开始先访问一个顶点所有的邻接顶点,然后按照顺序依次访问刚访问那一组邻接顶点的邻接顶点,就像一个树一样
(1)也需要一个访问标志visited数组
(2)需要设置一个队列Queue来记录已被访问的顶点顺序
(3)采用邻接表的存储方式表示图,整个算法分BFS和BFST
四 最小生成树
对于网络来说,弧一般都是带权的,,对于一个连通图G,其最小生成树指的是包括其所有顶点,以及部分边,满足边权总和最小,图是连通的这样一个。
1.普里姆算法
2.克鲁斯卡尔算法
五 DAG有向无环图
1.拓扑排序
通常把用顶点表示活动,弧表示活动间先后顺序的有向图称为顶点的网——AOV网
在AOV网中,若不存在回路,则所有顶点都可以排成一个序列,所有前驱活动都排在后驱活动的前面,这叫拓扑序列,由AOV网构造拓扑序列的过程叫做拓扑排序
方法:(1)在AOV网中选择一个入度为0的点把它写入拓扑序列中
(2)在AOV网中删除该点以及其所有的出边
结果:(1)网中全部顶点被输出,说明网中不存在回路(2)顶点未被全部输出,且网中不存在入度为0的顶点,说明有回路
采用邻接表作为有向图的存储结构,需要设置一个一维整型数组,用来保存图中每个顶点的入度值
算法:(1)将入度为0的顶点入栈
(2)从堆栈中弹出栈顶元素:
①输出栈顶元素
②把该顶点发出的所有有向边删去,即将所有后继顶点的入度减1,若减1后该顶点入度为0,则该顶点入栈
③重复②直到没有入度为0的顶点
实现:
2.关键路径
六 最短路径
1,.某一源头到各定点
2.每一对顶点之间