文章目录
一、概念
1.基本术语
术语 | 解释 |
---|---|
数据 | 对客观事物的符号表示 |
数据元素 | 数据的基本单位,由若干个数据项组成 |
数据对象 | 性质相同的数据元素的集合 |
数据结构 | 相互之间存在一种或多种特定关系的数据元素的集合 |
逻辑结构 | 数据之间关系的描述,分集合、线性结构、树形结构、图状结构 |
存储结构 | 逻辑结构在计算机中的表示,分顺序存储结构、链式存储结构 |
数据类型 | 一个值的集合和定义在该集合上的一组操作的总称,分原子类型、结构类型 |
抽象数据类型 | 一个数据模型和定义在该模型上的一组操作,分原子类型、固定聚合类型、可变聚合类型 |
2.算法
(1)五个特性:
输入性、输出性、有穷性、确定性、可执行性
(2)与程序的区别:
①程序不一定满足有穷性
②算法若用机器可执行的语句书写,则它是一个程序
③程序若对任何输入都不会陷入死循环,则它是算法
3.时间复杂度
语句在算法中重复执行的次数为语句频度
把频度看成问题规模n的某个函数f(n),则时间复杂度T(n)可表示为:T(n) = O(f(n))
一般情况下,时间复杂度指的是最坏情况下的时间复杂度
例:
for (i = 0; i < n; i++)
{
y++;
for (j = 0; j <= 2n; j ++)
{
x++;
}
}
x++的语句频度为n(2n+1)
T(n) = O(n2)
例:
x = 0
for (i = 1; i <= n; i++)
{
for (j = 1; j <= i; j++)
{
for (k = 1; k <= j; k++)
x += detal;
}
}
x += detal的语句频度为
n
(
n
+
1
)
(
n
+
2
)
6
\frac{n(n+1)(n+2)}{6}
6n(n+1)(n+2)
T(n) = O(n3)
常用的时间复杂度关系:
O ( 1 ) ≤ O ( l o g 2 ( n ) ) ≤ O ( n ) O ( n l o g 2 n ) ≤ O ( n 2 ) ≤ O ( n 3 ) ≤ … … ≤ O ( 2 n ) O(1)≤ O(log_2(n))≤O(n)O(nlog_2n)≤O(n^2)≤O(n^3)≤……≤ O(2^n) O(1)≤O(log2(n))≤O(n)O(nlog2n)≤O(n2)≤O(n3)≤……≤O(2n)
4.数据的逻辑结构
逻辑结构 | 特点 | 说明 | 举例 |
---|---|---|---|
集合 | 松散 | ||
线性结构 | 一对一 | 存在“第一个”和“最后一个”元素,除第一个外所有元素都有前驱,除最后一个外所有元素都有后继,数据元素之间只有线性关系 | 线性表,栈,队列,串 |
树形结构 | 一对多 | 反映元素间的层次关系和分支关系,每一层上的数据元素可能与下一层的多个数据元素有关系,但只和上一层中的一个元素有关系 | 树,二叉树 |
图状结构 | 多对多 | 任意两个数据元素之间都可能有关系 | 图 |
二、线性表
n个数据元素的有限序列
线性表中一个数据元素若由若干个数据项组成,则称数据元素为记录,线性表为文件
1.存储结构
(1)顺序存储
概念 | 把线性表中的所有元素按顺序依次存储到一块指定的连续的存储空间中 |
特点 | ①是一种随机存储的结构 ②常用数组来描述 |
优点 | ①数据元素可以随意存放 ②占用存储空间少 |
缺点 | ①需要一块地址连续的存储单元 ②插入和删除操作需要移动大量元素 |
在顺序存储结构的线性表中的某个位置插入和删除元素时,移动元素的个数主要取决于插入或删除元素的位置,假设在顺序表上任何位置插入和删除元素是等概率的,那么在表长为n的顺序表中插入和删除元素平均要移动其他元素 n 2 \frac{n}{2} 2n和 n − 1 2 \frac{n - 1}{2} 2n−1次,插入和删除算法的时间复杂度均为O(n)
//线性表的顺序存储结构
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
#define LIST_INCREMENT 10 //线性表存储空间的分配增量
typedef struct SqList//定义类型SqList,它的值为结构体型
{
ElemType *elem; //定义ElemType型的指针elem,指向SqList的基地址
int length; //线性表当前长度
int size; //线性表当前分配的存储容量(以sizeof(ElemType)为单位)
}SqList;
(2)链式存储
概念 | 由结点链结而成,每个结点为一个数据元素存储映像,包括数据域和指针域 |
特点 | ①头结点head为第一个结点之前附设的一个结点,其数据域为空 ②数据元素之间的逻辑关系由结点中的指针指示 |
形式 | ①单链表:每个结点只有一个指针域,next指针指向下一个结点,最后一个结点的next指针指向NULL ②循环链表:每个结点只有一个指针域,next指针指向下一个结点,最后一个结点的next指针指向head ③双向链表:每个结点有两个指针域,next指针指向直接后继,prior指针指向直接前驱,最后一个结点的next指针指向head |
优点 | ①不需要一大块地址连续的存储空间 ②插入和删除元素时不需要移动大量元素 ③不用固定最大长度 |
缺点 | ①占用存储空间大 ②不能随机访问数据元素 |
//线性表的链式存储结构
typedef struct lnode
{
ElemType data; //数据域:存储数据元素信息的域
struct lnode *next; //指针域:存储直接后继存储位置的域
}lnode, *LinkList; //定义类型lnode,它的值为结构体型,定义一个叫LinkList的lnode型指针
2.基本操作
(1)顺序存储
创建:
void InitList(SqList &L) //构造一个空的顺序表
{
L.elem = (ElemType*) malloc (LIST_INIT_SIZE * sizeof(ElemType)); //用指针elem开辟一个大小为LIST_INIT_SIZE * sizeof(ElemType)的存储空间,LIST_INIT_SIZE 为大小,sizeof(ElemType)为单位
if (!L.elem)
{
exit(OVERFLOW);
}
L.length = 0; //空表长度为0
L.size = LIST_INIT_SIZE ; //空表的初始存储容量
}
销毁:
void DestoryList(SqList &L) //销毁顺序表
{
L.length = 0;
free(L.elem);
L.elem = NULL; //指向空地址
}
清空:
void ClearList(SqList &L) //清空顺序表
{
L.length = 0;
free(L.elem);
L.elem = NULL; //指向空地址
L.elem = (ElemType*) malloc (LIST_INIT_SIZE * sizeof(ElemType)); //重新分配存储空间
}
判空:
bool ListEmpty(SqList L) //判断顺序表是否为空
{
return L.length == 0;
}
求长度:
int ListLength(SqList L) //返回顺序表的长度
{
return L.length;
}
取值:
Status GetElem(SqList L, int i) //取顺序表中下标为i的元素的值
{
ElemType e;
if (ListEmpty(L))
{
printf("this list is empty.\n");
return ERROR;
}
if (i <1 || i > L.length)
{
printf("this position is out of range.\n");
return ERROR;
}
e = L.elem[i]; //将elem[i]所指向的值赋给e
return e;
}
取下标:
Status LocateElem(SqList L, ElemType e) //返回顺序表中值为e的元素的下标
{
for (int i = 0; i < L.length; ++i)
{
if (L.elem[i] == e) //如果elem[i]所指向的值为e
{
return i; //下标
}
}
printf("this value is out of range.\n");
return ERROR;
}
插入:
Status Insert(SqList &L, int i, ElemType e) //在第i个元素前(即第i个位置)插入一个值为e的元素
{
ElemType *p, *q;
if (i < 1 || i > L.length + 1)
{
printf("please check wether the insert position is correct.\n");
return ERROR;
}
if (L.length >= L.size) //当前存储空间已满,增加分配
{
ElemType *newbase;
newbase = (ElemType*) realloc (L.elem, (L.size + LIST_INCREMENT) * sizeof(ElemType));
if (!newbase)
{
exit(OVERFLOW);
}
L.elem = newbase; //指向线性表的的基地址
L.size += LIST_INCREMENT; //加上存储空间的分配增量
}
q = &(L.elem[i-1]); //q存储第i个元素的地址,,即所插入元素的位置
for (p = &(L.elem[L.length - 1]); p >= q; --p) //将插入元素的位置后的元素往后移
{
*(p + 1) = *p; //将元素逐个往后移,直到移完第i个元素
}
*q = e; //插入e
++L.length; //顺序表的长度加一
return OK;
}
删除:
Status ElemDelete(SqList &L, int i, ElemType &e) //删除第i个元素,并用e返回它的值
{
ElemType *p,*q;
if (i < 1 || i > L.length + 1)
{
printf("please check wether the delete position is correct.\n");
return ERROR;
}
p = &(L.elem[i-1]); //p存储第i个元素的地址,即所删除元素的位置
e = *p; //用e存储第i个元素的值,可传出
++p; //p指向要删除元素的下一个元素
for (q = &(L.elem[L.length - 1]); p <= q; ++p) //将插入元素的位置后的元素往后移
{
*(p - 1) = *p; //将元素逐个往前移,直到移完最后一个元素
}
--L.length; //顺序表的长度减一
return OK;
}
合并:
void MergeList(SqList La, SqList Lb, SqList &Lc) //顺序表的合并(La,Lb均为元素递增顺序表,将La,Lb合并成递增顺序表Lc)
{
ElemType *pa, *pa_last, *pb, *pb_last, *pc;
Lc.size = Lc.length = La.length + Lb.length; //lc的表长为la + lb
Lc.elem = (ElemType*) malloc (Lc.size * sizeof(ElemType));
if(!Lc.elem)
{
exit(OVERFLOW);
}
pa = La.elem; //pa指向la首元素
pb = Lb.elem; //pb指向lb首元素
pa_last = La.elem + La.length - 1; //pa_last指向la尾元素
pb_last = Lb.elem + Lb.length - 1; //pb_last指向lb尾元素
pc = Lc.elem; //pc指向lc首元素
while (pa <= pa_last && pb <= pb_last) //直到把la和lb其中的一个遍历完结束循环
{
if (*pa <= *pb) //如果pa所指的值小于pb所指的值
{
*pc++ = *pa++; //将pa所指的值赋给pc再两者同时往后移
}
else
{
*pc++ = *pb++; //将pb所指的值赋给pc再两者同时往后移
}
}
while (pa <= pa_last) //若Lb已遍历完而La未遍历完
{
*pc++ = *pa++; //Lc插入La的剩余元素
}
while (pb <= pb_last) //若La已遍历完而Lb未遍历完
{
*pc++ = *pb++; //Lc插入Lb的剩余元素
}
}
显示:
void DisplayList(SqList &L) //显示顺序表
{
if (ListEmpty(L))
{
printf("this list is empty.");
}
else
{
for (int i = 0; i < L.length; ++i)
{
printf("%d ", L.elem[i]);
}
printf("\n");
}
}
(2)链式存储:
创建:
void CreateList(LinkList &L) //创建空的单链表
{
L = (LinkList) malloc (sizeof(lnode));
L->next = NULL; //创建空的单链表
}
判空:
bool ListEmpty(LinkList L) //判断单链表表是否为空
{
return L.next == NULL;
}
求长度:
int ListLength(LinkList L) //求单链表的长度
{
int n = 0;
lnode *p = L->next; //p指向单链表的第一个元素
while (p != NULL) //当指针移到链表尾之后结束循环
{
++n;
p = p->next; //指针往后移
}
return n;
}
取值:
Status GetElem(LinkList L, int i, ElemType &e) //取单链表的第i个元素
{
lnode *p = L->next; //p指向单链表的第一个元素
int j = 1;
while (p && j < i) //当p为空地址即链表为空或遍历到链表尾或j = i结束循环
{
p = p->next; //指针往后移,直到链表尾或位置i
++j;
}
if (!p || j > i)
{
return ERROR;
}
e = p->data;
return OK;
}
插入:
Status Insert(LinkList &L, int i, ElemType e) //在第i个元素前(即第i个位置)插入一个元素
{
lnode *p = L->next;
int j = 1;
while(p && j < i - 1) //当p为空地址即链表为空或遍历到链表尾或j = i - 1时结束循环
{
p = p->next; //指针后移,直到链表尾或位置i - 1
++j;
}
if (!p || j > i - 1)
{
return ERROR;
}
lnode *s;
s = (lnode*) malloc (sizeof(lnode)); //生成新结点
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
删除:
Status ListDelete(LinkList &L, int i, ElemType &e) //删除第i个元素
{
lnode *p = L->next;
int j = 1;
while(p && j < i - 1) //当p为空地址即链表为空或遍历到链表尾或j = i - 1时结束循环
{
p = p->next; //指针后移,直到链表尾或位置i - 1
++j;
}
if (!p || j > i - 1)
{
return ERROR;
}
lnode *q;
q = p->next;
p->next = q->next;
e = q->data;
free(q);
return OK;
}
合并:
void MergetList(LinkList La, LinkList Lb, LinkList &Lc) //有序表合并(表元素按值非递减排列)
{
Linklist Lc;
lnode *pa, *pb, *pc;
pa = La->next;
pb = Lb->next;
Lc = pc = La;
while (pa && pb) //当遍历完其中一个链表时结束循环
{
if (pa->data <= pb->data)
{
pc->next = pa;
pc = pa;
pa = pa->next;
}
else
{
pc->next = pa;
pc = pa;
pb = pb->next;
}
}
pc->next = pa? pa: pb; //把未遍历完的链表插到Lc后面
free(Lb);
}
三、栈
一种只能在表尾进行插入和删除操作的线性表,特点是先进后出
1.存储结构
(1)顺序栈
有两个指针,base指向栈底元素,top指向栈顶元素的下一个位置
栈空 | S.base == S.top |
栈满 | S.top - base >= S.stacksize |
入栈 | S.top++ |
出栈 | S.top– |
栈顶元素的值 | *(S.top - 1) |
//栈的顺序存储表示
#define Stack_Init_Size 100 //顺序栈的存储空间初始分配量
#define Stack_Increment 10 //顺序栈的存储空间分配增量
typedef struct sqstack //定义类型SqStack,它的值为结构体型
{
ElemType *base; //基地址
ElemType *top; //栈顶指针
int stacksize; //顺序栈当前已分配的存储空间(以sizeof(ElemType)为单位)
}SqStack;
(2)链栈
只允许在表头进行插入和删除的单链表,其表头指针称为栈顶指针
判空 | S->next == NULL |
入栈 | 将p所指的结点插到S所指的结点后 |
出栈 | 删去S->next所指的结点 |
//栈的链式存储结构
typedef int Status; //定义函数类型status,它的返回值为int型
typedef int ElemType; //定义元素类型ElemType,它的值为int型
typedef struct lnode //定义一个叫lnode类型,它的值为结构体型
{
ElemType data; //数据域:存储数据元素信息的域
struct lnode *next; //指针域:存储直接后继存储位置的域
}lnode, *LinkStack; //定义一个叫叫LinkStack的lnode型指针
2.基本操作
(1)顺序栈
构建:
void InitStack(SqStack &S) //构造空的顺序栈
{
S.base = (ElemType*) malloc (Stack_Init_Size * sizeof(ElemType));
if (!S.base)
{
exit(OVERFLOW);
}
S.top = S.base;
S.stacksize = Stack_Init_Size;
}
销毁:
void DestoryStack(SqStack &S) //销毁顺序栈
{
S.stacksize = 0;
free(S.base);
S.base = NULL;
S.top = S.base;
}
清空:
void ClearStack(SqStack &S) //清空顺序栈
{
S.stacksize = 0;
free(S.base);
S.base = NULL; //指向空地址
S.base = (ElemType*) malloc (Stack_Init_Size * sizeof(ElemType)); //重新为存放元素的变量开辟一个新的空间
S.top = S.base;
}
判空:
bool StackEmpty(SqStack &S) //判断栈是否为空
{
return S.top == S.base;
}
求长度:
int StackLength(SqStack &S) //求栈的长度
{
ElemType *p;
int n = 0;
for (p = S.base; p < S.top; ++p)
{
n++;
}
return n;
}
取栈顶元素:
Status GetTop(SqStack &S, ElemType &e) //用e返回栈顶元素的值
{
if (StackEmpty(S))
{
return ERROR;
}
e = *(S.top - 1);
return OK;
}
入栈:
Status Push(SqStack &S, ElemType e) //插入e为新的栈顶元素
{
if (S.top - S.base >= S.stacksize) //栈满,则追加存储空间
{
S.base = (ElemType*) realloc (S.base, (S.stacksize + Stack_Increment) * sizeof(ElemType));
if (!S.base)
{
exit(OVERFLOW);
}
// S.top = S.base + S.stacksize;
S.stacksize += Stack_Increment;
}
*S.top = e;
S.top++;
return OK;
}
出栈:
Status Pop(SqStack &S, ElemType &e) //删除栈顶元素并用e返回其值
{
if (StackEmpty(S))
{
return ERROR;
}
S.top--;
e = *S.top;
return OK;
}
(2)链栈
构建:
Status InitStack(LinkStack &S) //构造空的链栈
{
S = (LinkStack) malloc (sizeof(lnode));
if (!S)
{
exit(OVERFLOW);
}
S->next = NULL;
return OK;
}
判空:
bool StackEmpty(LinkStack &S) //判断栈是否为空
{
return S->next == NULL;
}
求长度:
int StackLength(LinkStack &S) //求栈的长度
{
int n = 0;
lnode *p = S->next;
while (p != NULL) //当指针移到链表尾之后结束循环
{
++n;
p = p->next; //指针往后移
}
return n;
取栈顶元素:
Status GetTop(LinkStack &S, ElemType &e) //用e返回栈顶元素的值
{
if (S->next == NULL)
{
return ERROR; //若栈为空,则错误
}
e = (S->next)->data;
return OK;
}
入栈:
Status Push(LinkStack &S, ElemType e) //插入e为新的栈顶元素
{
LinkStack p;
p = (LinkStack) malloc (sizeof(lnode));
if (!p)
{
exit(OVERFLOW);
}
p->data = e;
p->next = S->next; //将p插到s后
S->next = p;
return OK;
}
出栈:
Status Pop(LinkStack &S, ElemType &e) //删除栈顶元素并用e返回其值
{
if (S->next == NULL)
{
return ERROR; //若栈为空,则错误
}
LinkStack p;
p = S->next;
e = p->data;
S->next = p->next;
free(p);
return OK;
}
3.实际应用
(1)数制转换
void conversation() //十进制整数转八进制整数
{
ElemType e;
SqStack S;
InitStack(S);
scanf("%d", N);
while (N)
{
Push(S, N % 8);
N /= 8;
}
while (!StackEmpty(S))
{
Pop(S, e);
printf("%d", e)
}
}
(2)括号匹配
bool match(Hstring str) //[],()匹配
{
ElemType e;
SqStack S;
InitStack(S);
for (int i = 0; i < str.len; ++i)
{
if ((str[i] == '(') || ( str[i] == '[')) //放入左括号
{
Push(S, str[i]);
}
else
{
if (StackEmpty(S))
{
return False; //无左括号仍放右括号,错
}
else
{
GetTop(S, e);
if ((e == '(') && (str[i] == ')'))
{
Pop(S, e);
}
else if ((e == '[') && (str[i] == ']'))
{
Pop(S, e);
}
else
{
return FALSE; //左右括号不匹配,错
}
}
}
}
if (!StackEmpty())
{
return TRUE;
}
else
{
return FALSE; //仍有左括号未匹配,错
}
}
(3)行编辑(#:退格符,@:退行符)
void LineEdit()
{
SqStack S, T;
ElemType e;
char ch;
InitStack(S);
InitStack(T);
ch = getchar();
while (ch != EOF) //EOF为全文结束符
{
while (ch != EOF && ch != '\n')
{
switch (ch)
{
case '#': Pop(S, e); break;
case '@': ClearStack(S); break;
default: Push(S, ch); break;
}
ch = getchar(); //接收下一个字符
} //一行输入结束,最后一个字符为'\n'或'EOF'
while (!StackEmpty(S)) //最终S为空
{
Pop(S, e);
Push(T, e);
}
while (!StackEmpty(T)) //最终T为空
{
Pop(T, e);
printf("%c", e);
}
if (ch != EOF)
{
ch = getchar();
}
}
}
四、队列
一种只能在一端进行插入,在另一端进行删除的线性表,特点是先进先出
1.存储结构
(1)链队列
有两个指针的单链表,表头指针作为队头指针front用于删除,一个指向链表最后一个结点的指针作为队尾指针rear用于删除
判空 | Q.front == Q.rear |
入队 | 在Q.rear所指结点后插入一个结点 |
出队 | 删去Q.front->所指的结点 |
//队列的链式存储结构
typedef struct Qnode
{
ElemType data; //数据域
struct Qnode *next; //指针域
}Qnode, *QueuePtr; //定义类型Qnode,它的值为结构体型,定义一个叫QueuePtr的Qnode型指针
typedef struct LinkQueue //定义类型LinkQueue,它的值为结构体
{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
(2)顺序顺序队列
为一个大小为maxSize的数组,有两个指针,front指向队头元素,rear指向队尾元素的下一个位置
①一般顺序队列
判空 | Q.front = Q.rear = 0 |
队满 | Q.rear - Q.front = MaxSize |
入队 | Q.rear++ |
出队 | Q.front++ |
队头元素的值 | Q.base[Q.front] |
队尾元素的值 | Q.base[Q.rear - 1] |
②循环队列
判空 | Q.front = Q.rear |
队满 | (Q.rear + 1) % MaxSize == Q.front |
入队 | Q.rear = (Q.rear + 1) % MaxSize; |
出队 | Q.front = (Q.front + 1) % MaxSize |
队头元素的值 | Q.base[Q.front] |
队尾元素的值 | Q.base[Q.rear - 1] |
//队列的顺序存储表示
#define MaxSize 100 //队列的最大存储空间
typedef struct SqQueue//定义类型SqQueue,它的值为结构体型
{
ElemType *base; //或base[MaxSize]
int front, rear;
}SqQueue;
2.基本操作
(1)链队列
构造:
Status InitQueue(LinkQueue &Q) //构造空队列
{
Q.front = Q.rear = (QueuePtr) malloc (sizeof(Qnode));
if (!Q.front)
{
exit(OVERFLOW);
}
Q.front->next = NULL;
return OK;
}
销毁:
void DestoryQueue(LinkQueue &Q)
{
while (Q.front)
{
Q.rear = Q.front->next;
free(Q.front);
Q.front = Q.rear;
}
}
清空:
void ClearQueue(LinkQueue &Q) //清空队列
{
QueuePtr p;
p = Q.front->next;
free(p);
Q.front = Q.rear = (QueuePtr) malloc (sizeof(Qnode));
Q.front->next = NULL;
}
判空:
bool QueueEmpty(LinkQueue &Q) //判断队列是否为空
{
return Q.front == Q.rear;
}
求长度:
int QueueLength(LinkQueue &Q) //求队列长度
{
int n = 0;
QueuePtr p = Q.front->next;
while (p != NULL) //当指针移到链表尾之后结束循环
{
++n;
p = p->next; //指针往后移
}
return n;
}
取队头元素的值:
Status GetHead(LinkQueue &Q, ElemType &e) //用e返回队头元素的值
{
if (Q.front == Q.rear)
{
return ERROR; //若队为空,则错误
}
e = Q.front->next->data;
return OK;
}
取队尾元素的值:
Status GetTail(LinkQueue &Q, ElemType &e) //用e返回队尾元素的值
{
if (Q.front == Q.rear)
{
return ERROR; //若队为空,则错误
}
e = Q.rear->data;
return OK;
}
入队:
Status EnQueue(LinkQueue &Q, ElemType e) //插入元素e作为Q的队尾元素(入队)
{
QueuePtr p;
p = (QueuePtr) malloc (sizeof(Qnode));
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
出队:
Status DeQueue(LinkQueue &Q, ElemType &e) //删除Q的队头元素(出队)
{
if (Q.front == Q.rear)
{
return ERROR;
}
QueuePtr p = Q.front->next;
e = p->data;
Q.front->next = p->next;
if (Q.rear == p)
{
Q.rear = Q.front;
}
free(p);
return OK;
}
(2)顺序队列
构造:
Status InitQueue(SqQueue &Q) //构建一个空队列
{
Q.base = (ElemType*) malloc (MaxSize * sizeof(ElemType));
if (!Q.base)
{
exit(OVERFLOW);
}
Q.front = Q.rear;
return OK;
}
清空:
void ClearQueue(SqQueue &Q) //清空队列
{
free(Q.base);
Q.base = NULL;
Q.base = (ElemType*) malloc (MaxSize * sizeof(ElemType));
Q.front = Q.rear;
}
判空:
bool QueueEmpty(SqQueue &Q) //判断队列是否为空
{
return Q.front = Q.rear;
}
求长度:
int QueueLength(SqQueue Q) //求队列的长度
{
int l;
l = (Q.rear - Q.front + MaxSize) % MaxSize;
return l;
}
取队头元素:
Status GetHead(SqQueue &Q, ElemType &e) //用e返回队头元素的值
{
if (Q.front == Q.rear) //空队列
{
return ERROR;
}
e = Q.base[Q.front];
return OK;
}
取队尾元素:
Status GetTail(SqQueue &Q, ElemType &e) //用e返回队尾元素的值
{
if (Q.front == Q.rear) //空队列
{
return ERROR;
}
e = Q.base[Q.rear - 1];
return OK;
}
入队:
Status EnQueue(SqQueue &Q, ElemType e) //插入元素(入队)
{
if ((Q.rear + 1) % MaxSize == Q.front) //队列满
{
return ERROR;
}
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1) % MaxSize;
return OK;
}
出队:
Status DeQueue(SqQueue &Q, ElemType &e) //删除元素(出队)
{
if (Q.front == Q.rear) //空队列
{
return ERROR;
}
e = Q.base[Q.front];
Q.front = (Q.front + 1) % MaxSize;
return OK;
}
五、串
由零个或多个字符组成的有限序列,其逻辑结构与线性表相似,区别在于串的数据约束对象为字符集,在基本操作上,串多以“串的整体”作为对象,而线性表多以“单个元素”作为操作对象
空串 | 由零个字符组成的串 |
空格串 | 由一个或多个空格组成的串 |
子串 | 串中任意连续个字符组成的子序列称该串的子串,该串称主串 |
1.存储结构
(1)顺序存储
①定长顺序存储
概念 | 用一组地址连续的存储单元存储串,可用定长数组表示,数组的零号单元可用来表示串的长度 |
缺点 | 规定了串的最大长度 |
//串的定长顺序存储结构
#define MAXSTRLEN 255
typedef unsigned char Sstring[MAXSTRLEN + 1]
②堆分配存储
概念 | 用一块自由存储区来存储串,用malloc()和free()来管理 |
优点 | 对串长无限制 |
//串的堆分配存储
typedef struct Hstring//定义一个叫Hstring的结构体
{
char *ch; //串指针
int length; //串长度
}Hstring;
(2)链式存储
(块链存储)
特点 | 块链结点大小大于等于1 |
优点 | 不需要大块连续空间 |
缺点 | 占用存储量大,操作复杂,不如顺序存储方便 |
//串的块链存储表示
#define CHUNSIZE 80 //自定义块大小
typedef struct Chunk
{
char ch[CHUNSIZE];
struct Chunk *next;
}Chunk;
typedef struct LString
{
Chunk *head, *tail; //串的头和尾
int curlen; //串的当前长度
}LString;
2.基本操作
(1)堆分配存储
串赋值:
Status StrAssign(Hstring &S, char *chars) //赋值操作
{
int len, i;
char *c = chars; //指向chars串的指针
for (len = 0; *c != '\0'; ++len, ++c); //求chars串的长度
if (!len) //len = 0, 将S为空串,指针指向空地址,串的长度为0
{
S.ch = NULL;
S.length = 0;
}
else //len > 0
{
S.ch = (char*) malloc (len * sizeof(char));
if (!S.ch)
{
exit(OVERFLOW);
}
for (i = 0; i < len; ++i) //将chars串的字符重新赋值给串
{
S.ch[i] = chars[i];
}
S.length = len; //i为已赋值的chars串的长度
}
return OK;
}
求串长:
int StrLength(Hstring S) //求串长度
{
return S.length;
}
清空串:
Status ClearStr(Hstring &S) //清为空串
{
if (S.ch)
{
free(S.ch); //释放S原有的空间
S.ch = NULL;
}
S.length = 0;
return ok;
}
比较串:
int StrCompare(Hstring S, Hstring T) //比较两个串的大小
{
int i;
for (i = 0; i < S.length && i < T.length; ++i) //逐个比较两个串中的字符,直到将其中的一个串遍历完
{
if (S.ch[i] != T.ch[i])
{
return S.ch[i] - T.ch[i]; //若不等,返回这两个字符比较的结果
}
}
return S.length - T.length; //若所比较的字符均相等,长度更长的字符串更大
}
联接串:
Status ConcatStr(Hstring &S, Hstring S1, Hstring S2) //联接串
{
S.ch = (char*) malloc ((S1.length + S2.length) * sizeof(char));
if (!S.ch)
{
exit(OVERFLOW);
}
int i, j;
for (i = 0; i < S1.length; ++i)
{
S.ch[i] = S1.ch[i];
}
for (i = S1.length, j = 0; j < S2.length; ++i, ++j)
{
S.ch[i] = S2.ch[j];
}
S.length = S1.length + S2.length;
return OK;
}
求子串:
Status SubStr(Hstring S, Hstring &T, int pos, int len) //求子串(返回从第pos字符起长度为len的子串)
{
if (pos < 1 || pos > S.length || len < 0 || len > S.length - pos + 1)
{
return ERROR;
}
if (!len) //空子串
{
T.ch = NULL;
T.length = 0;
}
else
{
T.ch = (char*) malloc (len * sizeof(char));
int i, j;
for (i = 0, j = pos - 1; i < len; ++i, ++j)
{
T.ch[i] = S.ch[j];
}
T.length = len;
}
return OK;
}
插入串:
Status StrInsert(Hstring &S, Hstring T, int pos) //插入串(在S的第pos个字符前插入串T)
{
if (pos < 1 || pos > S.length + 1)
{
return ERROR;
}
if (T.length) //若T为空串则不操作
{
int i;
char *p;
p = (char*) malloc ((S.length + T.length) * sizeof(char));
if (!p)
{
exit(OVERFLOW);
}
for (i = 0; i < pos - 1; ++i) //将S原先第pos个字符前的元素放入p
{
p[i] = S.ch[i];
}
for (i = 0; i < T.length; ++i) //在第pos-1个元素后插入串T
{
p[pos - 1 + i] = T.ch[i];
}
for (i = pos - 1; i < S.length; ++i) //将S原先第pos个字符及它之后的元素放入p
{
p[T.length + i] = S.ch[i];
}
S.length += T.length;
free(S.ch);
S.ch = p;
}
return OK;
}
删除子串:
Status StrDelete(Hstring &S, int pos, int len) //删除子串(从S中删除第pos字符起长度为len的子串)
{
if (pos < 1 || pos + len > S.length + 1)
{
return ERROR;
}
if (S.length)
{
int i;
for (i = pos - 1; i < S.length - len; i++)
{
S.ch[i] = S.ch[i + len];
}
S.length = S.length - len;
}
return OK;
}
(2)串的模式匹配
①利用串的最小子集的算法
int Index(String S,String T,int pos) //子串T在主串S中第pos个字符后的定位,若不存在返回0
{
int i, m, n;
if (pos>0)
{
n = StrLength(S);
m = StrLength(T);
i = pos;
while (i <= n - m +1)
{
SubString(sub,S,i,m);
if (StrCompare(sub,T)!=0)
{
++i;
}
else
{
return i;
}
}
}
return 0;
}
②不利用最小操作子集的算法(用定长顺序存储)
int Index(SString S,SString T,int pos) //子串T在主串S中第pos个字符后的定位,若不存在返回0
{
int i, j;
i = pos;
j = 1;
while (i <= S[0] && j <= T[0]) //0号单元存放串的长度
{
if (S[i] == T[j])
{
++i;
++j;
}
else //指针后退重新匹配
{
i = i - j + 2;
j = 1;
}
}
if (j>T[0]) //匹配完成
{
return i-T[0];
}
else
{
return 0;
}
}
六、矩阵
1.存储结构
(1)顺序存储
对于 m × n m\times n m×n的矩阵, 0 ≤ i ≤ m − 1 0\leq i \leq m - 1 0≤i≤m−1, 0 ≤ j ≤ n − 1 0\leq j \leq n - 1 0≤j≤n−1
若以行序为主序,则Loc(i, j) = Loc(0, 0) + (n * i + j) × \times ×L
若以列序为主序,则Loc(i, j) = Loc(0, 0) + (m * ij+ i) × \times ×L
①对称矩阵
[
1
0
1
0
1
0
1
0
0
]
\left[ \begin{matrix} 1&0&1\\ 0&1&0\\ 1&0&0 \end{matrix}\right]
⎣⎡101010100⎦⎤
性质:
n
×
n
n\times n
n×n的矩阵,
a
i
j
=
a
j
i
,
1
≤
i
,
j
≤
n
a_{ij} = a_{ji},1\leq i,j \leq n
aij=aji,1≤i,j≤n
压缩存储:将主对角线及其以上或以下的元素存储到一维数组中
下三角元素 a i j a_{ij} aij与一维数组Sa[k]的关系: k = i ( i − 1 ) 2 + j − 1 , 0 ≤ k ≤ n ( n + 1 ) 2 − 1 k = \frac{i (i - 1)}{2} + j - 1,0 \leq k \leq \frac{n(n + 1)}{2} - 1 k=2i(i−1)+j−1,0≤k≤2n(n+1)−1
上三角元素 a i j a_{ij} aij与一维数组Sa[k]的关系: k = j ( j − 1 ) 2 + i − 1 , 0 ≤ k ≤ n ( n + 1 ) 2 − 1 k = \frac{j (j - 1)}{2} + i - 1,0 \leq k \leq \frac{n(n + 1)}{2} - 1 k=2j(j−1)+i−1,0≤k≤2n(n+1)−1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kcs4iDkv-1642249547004)(C:\Users\86136\Desktop\学习资料\笔记图片\对称矩阵.jpg)]
②对角矩阵
[
1
1
0
1
1
1
0
1
1
]
\left[ \begin{matrix} 1&1&0\\ 1&1&1\\ 0&1&1 \end{matrix}\right]
⎣⎡110111011⎦⎤
性质:
n
×
n
n\times n
n×n的矩阵,所有非零元素集中在以对角线为中心的带状区域中
压缩存储:将非零元素存储到一维数组中
非零元素数目最多为: ( n − 2 ) × 3 + 4 (n - 2) \times 3 + 4 (n−2)×3+4
③稀疏矩阵
性质:矩阵中大部分元素的值为0,元素分布无规律
若 m × n m\times n m×n的矩阵中有t个不为0的元素,则稀疏因子 σ = t m × n \sigma = \frac{t}{m \times n} σ=m×nt,若 σ ≤ 0.05 \sigma \leq 0.05 σ≤0.05,称稀疏矩阵
压缩存储:
Ⅰ三元组顺序存储
//一个矩阵元素的存储表示
#define MAXSIZE 12500 //最大元素个数
typedef struct
{
int i, j; //行号,列号
ElemType e; //元素
}Triple;
//矩阵的存储表示
typedef struct
{
Triple data[MAXSIZE + 1];
int m, n, t; //行数,列数,非零元素个数
}TSMatrix;
//行逻辑链接存储表示
typedef struct
{
Triple data[MAXSIZE + 1];
int m, n, t; //行数,列数,非零元素个数
}TSMatrix;
使用行逻辑链接:
//一个矩阵元素的存储表示
#define MAXSIZE 12500 //最大元素个数
#define MAXRC 250 //最大行数
typedef struct
{
int i, j; //行号,列号
ElemType e; //元素
}Triple;
//行逻辑链接存储表示
typedef struct
{
Triple data[MAXSIZE + 1];
int rpos[MAXRC + 1];//各行第一个非零元素位置的数组
int m, n, t; //行数,列数,非零元素个数
}TSMatrix;
Ⅱ十字链表存储
//十字链表存储表示
typedef struct OLNode
{
int i, j; //行号,列号
ElemType e; //元素
struct QLNode *right, *down;
}OLNode, *Olink;
typedef struct
{
Olink *rhead, *chead; //行指针,列指针
int m, n, t; //行数,列数,非零元素个数
}CrossList
七、广义表
LS = (a1, a2, …, an)
若ai为单个数据元素,则称为原子
若ai为一个广义表,则称子表
括号的层数称深度
对于非空表,a1是表头,(a2, a3, …, an)是表尾
例如:
A = () 空表,长度为0,深度为1
B = (()) 有一个子表的广义表,长度为1,深度为2,表头为(),表尾为()
C = (a, b) 有两个原子的广义表,长度为2,深度为1,表头为a,表尾为(b)
D = ((a, b), c) 有一个子表,一个原子的广义表,长度为2,深度为2,表头为(a, b),表尾为©
E = (a, E) 递归表,相当于(a,(a,(a,…)))
1.存储结构
(链式存储)
//广义表的头尾链表存储表示
typedef enum {ATOM, LIST}ElemTag; //ATOM == 0,原子;LIST == 1, 子表
typedef struct GLNode
{
ElemTag tag; //用于区分原子和子表
union
{
AtomType atom; //原子值域
struct
{
struct GLNode *hp, *tp; //表头表尾指针
}ptr; //ptr表示指针域,ptr.head和ptr.hp表示指向表头和表尾的指针
};
}*Glist;
//广义表的头尾链表存储表示
typedef enum {ATOM, LIST}ElemTag; //ATOM == 0,原子;LIST == 1, 子表
typedef struct GLNode
{
ElemTag tag; //用于区分原子和子表
union
{
struct
{
AtomType atom; //原子值域
struct GLNode *hp; //表头指针
};
struct GLNode *tp; //指向下一个元素结点的指针
};
}*Glist;
八、树
树是有n个结点的有限集合,满足两个条件:
①有且只有一个根结点
②其余结点为m棵互不相交的有限集合,每个集合都是一颗树,称子树
解释 | |
---|---|
树的结点 | 包含一个数据元素及若干个指向它的子树的分支 |
结点的度 | 结点拥有子树的个数 |
树的度 | 树中所有结点的度的最大值 |
分支结点 | 度大于0的结点 |
叶子 | 度为0的结点 |
结点的孩子 | 结点子树的根,该节点为孩子的双亲 |
兄弟 | 同一个双亲的孩子 |
堂兄弟 | 双亲在同一层的结点 |
祖先 | 从根到该结点所经分支上的所有结点 |
子孙 | 一个结点的所有子树中的结点 |
结点的层次 | 根为第一层,其孩子结点为第二层… |
树的深度 | 树中结点的最大层次数 |
有序树 | 树中结点的各子树从左至右看成有序的 |
森林 | 0个或多个互不相交的树的集合 |
1.存储结构
孩子兄弟表示法(二叉树表示法)
Typedef struct CSNode
{
ElemType data;
struct CSNode *firstchild,*next sibling;
}CSNode, *CSTree;
树的遍历
树的遍历 | ||
---|---|---|
先根遍历 | 先访问树的根结点,再依次遍历根的每棵子树 | 与转换成二叉树的先序遍历相同 |
后根遍历 | 先依次遍历根的每棵子树,再访问树的根结点 | 与转换成二叉树的中序遍历相同 |
森林的遍历
森林的遍历 | |
---|---|
先序遍历 | 每棵树按先根遍历 |
中序遍历 | 每棵树按后根遍历 |
九、二叉树
每个结点至多有两棵子树,子树有左右之分
性质:
①在二叉树的第i层上至多有 2 i − 1 2^{i - 1} 2i−1个结点
②深度为k的二叉树至多有 2 k − 1 2^{k}-1 2k−1个结点
③非空二叉树的叶子结点数等于双分支结点数加一
特殊的二叉树:
①满二叉树
深度为k且有 2 k − 1 2^{k}-1 2k−1个结点,第i层上有 2 i − 1 2^{i - 1} 2i−1个结点
②完全二叉树
叶子结点只能在最下层或次最下层出现
任意结点右分支的子孙的最大层次为L,则其左分支的子孙的最大层次为L+1,即最下层的叶子结点集中在树的左部
性质:
Ⅰ有n个结点的完全二叉树的深度为 ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n\rfloor +1 ⌊log2n⌋+1
Ⅱ将结点按层编号(从上至下,从左至右),对任意结点i,有:
若i = 1,则结点为根;若i>1,其双亲为结点 ⌊ i 2 ⌋ \lfloor \frac{i}{2}\rfloor ⌊2i⌋
若2i>n,则结点i无左孩子;否则其左孩子为结点2i
若2i+1>n,则结点i无右孩子;否则其右孩子为结点2i+1
1.存储结构
(1)顺序存储
根结点编号为1,第i个结点的左儿子编号为2i,右孩子编号为2i+1
(2)(链式存储)
①二叉链表存储
//二叉树的二叉链表存储
typedef struct BiTNode
{
TElemType data; //该结点的数据域
struct BiTNode *lchild, *rchild; //该结点的左右孩子指针
}BiTNode, *BiTree;
②三叉链表存储
//二叉树的三叉链表存储
typedef struct TriTNode
{
TElemType data; //该结点的数据域
struct TriTNode *lchild, *rchild; //该结点的左右孩子指针
truct TriTNode *parent;
}TriTNode, *TriTree;
③二叉线索存储表示
//二叉线索存储表示
typedef enum PointerTag{Link, Thread}; //Link = 0, Thread = 1
typedef struct BiThrNode
{
TElemType data; //该结点的数据域
struct BiThrNode *lchild, *rchild; //该结点的左右孩子指针
PointerTag LTag, RTag; //左右标记
}BiThrNode, *BiThrTree;
2.基本操作
构建:
Status CreateBiTree(BiTree &T) //构造二叉树,按先序输入二叉树每个结点的值(一个字符),'*'表示空树
{
TElemType ch;
scanf("%c", &ch);
if (ch == '*') //'*'表示空树
{
T = NULL;
}
else
{
T = (BiTNode*) malloc (sizeof(BiTNode));
if (!T)
{
exit(OVERFLOW);
}
T->data = ch;
CreateBiTree(T->lchild); //构造左子树
CreateBiTree(T->rchild); //构造右子树
}
return OK;
}
先序:根左右
void PreOrderTraverse(BiTree T) //先序遍历(根左右)递归实现
{
if (T)
{
Visit(T->data);
PreOrderTraverse(T->lchild); //遍历左子树
PreOrderTraverse(T->rchild); //遍历右子树
}
}
中序:左根右
递归实现:
void InOrderTraverse(BiTree T) //中序遍历(左根右)递归实现
{
if (T)
{
InOrderTraverse(T->lchild); //遍历左子树
Visit(T->data);
InOrderTraverse(T->rchild); //遍历右子树
}
}
非递归实现:(用栈实现)
Status InOrderTraverse2(BiTree T) //中序遍历(左根右)非递归实现
{
LinkStack S;
BiTNode *p;
InitStack(S);
Push(S, T); //根指针进栈
while (!StackEmpty(S))
{
while (GetTop(S, p) && p)
{
Push(S, p->lchild); //向左走到尽头
}
Pop(S, p); //空指针退栈
if (!StackEmpty(S))
{
Pop(S, p);
if (!Visit(p->data))
{
return ERROR;
}
Push(S, p->rchild);
}
}
return OK;
}
后序:左右根
void LastOrderTraverse(BiTree T) //后序遍历(左右根)递归实现
{
if (T)
{
LastOrderTraverse(T->lchild); //遍历左子树
LastOrderTraverse(T->rchild); //遍历右子树
Visit(T->data);
}
}
层序:(用队列实现)
void BFSTraverse(BiTree T) //层序遍历
{
LinkQueue Q;
BiTNode *p;
InitQueue(Q);
if (T)
{
EnQueue(Q, T); //根结点入队列
}
while (!QueueEmpty(Q))
{
DeQueue(Q, p); //队头元素出队并置为p
Visit(p->data);
if (p->lchild)
{
EnQueue(Q, p->lchild); //左子树根入队列
}
if (p->rchild)
{
EnQueue(Q, p->rchild); //右子树根入队列
}
}
}
求叶子结点数:
Status CountLeaf(BiTree T) //叶子结点个数
{
if (T)
{
if ((!T->lchild) && (!T->rchild))
{
count++;
}
CountLeaf(T->lchild);
CountLeaf(T->rchild);
}
return OK;
}
求深度:
int Depth(BiTree T) //深度
{
int depthval, m, n;
if (!T)
{
depthval = 0;
}
else
{
m = Depth(T->lchild);
n = Depth(T->rchild);
depthval = 1 + (m > n? m: n);
}
return depthval;
}
3.实际应用
路径 | 从树中一个结点到另一个结点之间的分支 |
路径长度 | 路径上的分支数 |
树的路径长度 | 从树根到树中每一个结点的路径长度之和 |
结点的权 | 树中的结点上赋予的一定的实数 |
带权路径长度 | 从结点到根之间的路径长度与结点上权值的乘积 |
树的带权路径长度 | 树中所有叶子结点的带权路径长度之和 |
哈夫曼树的构造,编码
(哈夫曼树是一棵正则二叉树,没有度为1的结点)
构造哈夫曼树:
void CreateHuffmanTree(HuffmanTree &HT, int n, int *w) //建一棵有n个叶子结点的赫夫曼树, 各叶子的权值对应数组w中的元素
{
int m = 2 * n - 1; //m为赫夫曼树的总结点数
int idx1, idx2;
int i;
if (n < 0)
{
return; //树的结点为0,即空树
}
/*二叉树初始化*/
HT = (HuffmanTree) malloc ((m + 1) * sizeof (HTNode)); //由于0号元素不使用,因此需要分配m+1个空间
for (i = 1; i <= m; ++i)
{
HT[i].parent = HT[i].lchild = HT[i].rchild = 0; //初始化指向双亲,左孩子,右孩子的游标为0
}
for (i = 1; i <= n; ++i)
{
HT[i].weigjt = w[i]; //放入各叶子结点的权值
}
/*建赫夫曼树*/
for (i = n + 1; i <= m; ++i)
{
Select(HT, i - 1, idx1, idx2); //在HT[k](1 <= k <= i - 1)中选择两个双亲为0且权值最小的结点,并返回它们在HT中的下标idx1, indx2
HT[idx1].parent = HT[idx2].parent = i; //得到新结点i,从森林中删除idx1和idx2,修改它们的双亲为i
HT[i].lchild = idx1;
HT[i].rchild = idx2;
HT[i].weigjt = HT[idx1].weigjt + HT[idx2].weigjt;
}
}
void Select(HuffmanTree HT, int k, int &idx1, int &idx2) //在HT[k](1 <= k <= i - 1)中选择两个双亲为0且权值最小的结点,并返回它们在HT中的下标idx1, indx2
{
int min1, min2; //用min1,min2分别存储第一小和第二小的下标
min1 = min2 = 9999999;
for (int i = 1; i <= k; ++i)
{
if (HT[i].parent == 0 && min1 > HT[i].weigjt)
{
if (min1 < min2)
{
min2 = min1;
idx2 = idx1;
}
min1 = HT[i].weigjt;
idx1 = i;
}
else if (HT[i].parent == 0 && min2 > HT[i].weigjt)
{
min2 = HT[i].weigjt;
idx2 = i;
}
}
}
由哈夫曼树求哈夫曼编码
void CreateHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n) //由赫夫曼树求赫夫曼编码
{
int start;
int c, f;
HC = (char**) malloc ((n + 1) * sizeof(char*)); //分配n个字符编码的存储空间,存储字符编码表
char *cd;
cd = (char*) malloc (n * sizeof(char)); //临时存储每个字符的编码的数组
cd[n - 1] = '\0'; //放置结束符
for (int i = 1; i <= n; ++i)
{
start = n - 1; //start为指向编码结束符的游标
c = i;
f = HT[i].parent; //f指向c结点的双亲
while (f != 0) //从叶结点向上回溯,直到根节点(根节点指向双亲的游标为0)
{
--start; //回溯start前一个位置
if (HT[f].lchild == c) //判断c结点是f的哪个孩子
{
cd[start] = '0'; //左孩子,编码0
}
else
{
cd[start] = '1'; //右孩子,编码1
}
c = f;
f = HT[f].parent; //向上回溯
}
/*此时已求出第i个字符的编码*/
HC[i] = (char*) malloc ((n - start) * sizeof (char)); //为第i个字符编码分配空间
strcpy(HC[i], &cd[start]); //将编码拷贝
}
free(cd); //释放工作空间
}
十、图
顶点个数:n
无向完全图:有 1 2 n ( n − 1 ) \frac{1}{2}n(n - 1) 21n(n−1)条边
有向完全图:有 n ( n − 1 ) n(n - 1) n(n−1)条边
弧头:有箭头;弧尾:无箭头
所有边的度数和=边数 × 2 \times2 ×2
路径:一个顶点到另一个顶点的顶点序列
简单路径:路径上的顶点不重复
回路:一个顶点回到它本身的顶点序列
简单回路:回路上的顶点不重复
连通图:无向图任意两个顶点都存在路径
连通分量:无向图中的极大连通子图
强连通图:有向图任意两个顶点都相互存在路径
强连通分量:有向图中的极大强连通子图
生成树:含有全部n个顶点但只有n-1条边的的极小连通子图
最小生成树:边的权值最小的生成树
1.存储结构
(1)顺序存储
(邻接矩阵)
(2)链式存储
①邻接表
②逆邻接表
③十字链表
④邻接多重表
2.基本操作
①深度优先遍历
从某个顶点出发,访问所有和该顶点相通的顶点,若图中仍有顶点未被访问,则以该顶点作为起点,不断重复直到图中所有顶点都被访问
时间复杂度:
邻接矩阵: O ( n 2 ) O(n^2) O(n2)
邻接表: O ( e + n ) O(e + n) O(e+n)
②广度优先遍历
从某个顶点出发,由近到远依次访问和该顶点路径相通且长度为1,2,…,的顶点
时间复杂度:
邻接矩阵: O ( n 2 ) O(n^2) O(n2)
邻接表: O ( e + n ) O(e + n) O(e+n)
③prim算法
从某个顶点出发,把这个顶点加到顶点集,把与该顶点集中顶点所构成的代价最小的边的顶点加入顶点集,直到把所有顶点加入到顶点集中
目的:求最小代价生成树
特点:将顶点归并,与边数无关,适用于稠密网
时间复杂度: O ( n 2 ) O(n^2) O(n2)
④kruskal算法
依次把代价最小的边的顶点加入到顶点集,直到这些顶点构成一个连通分量
目的:求最小代价生成树
特点:将边归并,与顶点无关,适用于稀疏网
时间复杂度: O ( e l o g e ) O(eloge) O(eloge)
⑤Dijsktra算法
Ⅰ从指定顶点开始,在它所有可达且未找到最短路径的顶点中找离他最近的顶点,此时该顶点的距离即为最短路径
Ⅱ再找该顶点所有可达的顶点,若指定顶点经该顶点到达某顶点的距离小于指定顶点直达的距离,则将这个距离纳入比较范围
Ⅲ重复ⅠⅡ直到找到所有最短路径
目的:求某一顶点到其余各顶点的最短路径
void ShortestPath_DIJ(MGraph G, VertexType V0, PathMatrix &P, ShortPathTable &D) //用Dijkstra算法求有向网G的v0顶点到其余各顶点v的最短路径P[v]及其带权长度D[v]
{
int v0 = LocateVex(G, V0); //v0存储V0顶点的位置
int v, w; //v,w存储其余顶点的位置
int final[MAX_VERTEX_NUM]; //final为true表示获得V0到V的最短路径
for (v = 0; v < G.vexnum; ++v)
{
final[v] = false; //初始化,假设刚开始时未找到V0到V的最短路径
D[v] = G.arcs[v0][v]; //v0到其余各顶点的直达路径
CreateList(P[v]); //初始化,路径为空
if (D[v] < INFINITY) //若V0能直达到V
{
InsertElement(P[v], v0); //把V0放入路径
InsertElement(P[v], v); //把V放入路径
}
}//初始化
//开始主循环,每次求得V0到某个V顶点的最短路径
for (int i = 1; i < G.vexnum; ++i) //其余的顶点,i用于控制循环次数
{
int min = INFINITY; //min为当前已知的距V0顶点最近的距离
for (w = 0; w < G.vexnum; ++w)
{
if (!final[w]) //若未求得V0到W的最短距离
{
if (D[w] < min) //若V0到W的距离小于min
{
v = w; //V为当前距V0距离最近的顶点
min = D[w]; //更新min
}
}
} //该循环结束后得到距离V0最近的点V
final[v] = true; //获得V0到V的最短路径
for (w = 0; w < G.vexnum; ++w) //更新当前的最短路径
{
if (G.arcs[v][w] != INFINITY) //若有V到W的边,则处理
{
if (!final[w] && (min + G.arcs[v][w] < D[w])) //若未求得V0到W的最短距离且V0到V的最短距离加上V到W的直达距离小于V0到W的当前最小距离
{
D[w] = min + G.arcs[v][w]; //让V0到V的最短距离加上V到W的直达距离成为V0到W的新的最小距离
CopyList(P[w], P[v]); //更新V0到W的路径
InsertElement(P[w], w); //把W放入路径
}
}
}
}
}
⑥Floyd算法
若某两个顶点经某一顶点到达的距离更短,则更新最短距离与路径
目的:求任意两顶点之间的最短路径
void ShortestPath_Floyd(MGraph G, PathMatrix &P, DistanceMartix &D) //用Floyd算法求有向网G的任意两个顶点之间的最短路径矩阵P及距离矩阵D
{
int v, w, u, i;
for (v = 0; v < G.vexnum; ++v)
{
for (w = 0; w < G.vexnum; ++w)
{
D[v][w] = G.arcs[v][w]; //把直达距离放入距离矩阵
CreateList(P[v][w]);
if (D[v][w] < INFINITY)
{
InsertElement(P[v][w], v); //把v放入路径
InsertElement(P[v][w], w); //把w放入路径
}
}
} //初始化
//主循环
for (u = 0; u < G.vexnum; ++u)
{
for (v = 0; v < G.vexnum; ++v)
{
for (w = 0; w < G.vexnum; ++w)
{
if ((D[v][u] != INFINITY) && (D[u][w] != INFINITY) && (v != w)) //若v经u到w的一条路径更短
{
if (D[v][u] + D[u][w] < D[v][w]) //从v经u到w的一条路径更短
{
D[v][w] = D[v][u] + D[u][w]; //更改距离
MergetList(P[v][u], P[u][w], P[v][w]);
}
}
}
}
}
}
⑦拓扑排序
Ⅰ在无向图中找一个无前驱的顶点输出
Ⅱ从图中删除该顶点和所有以它为尾的弧
Ⅲ重复ⅠⅡ直到输出所有顶点或当前图没有无前驱的顶点(说明存在回路)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xtCRjuj8-1642249547015)(C:\Users\86136\Desktop\学习资料\笔记图片\拓扑.jpg)]
⑧关键路径
AOE网:顶点表示事件,弧表示活动,权表示持续的时间,可用于估算工程的完成时间
AOE网的性质:
Ⅰ只有某顶点代表的事件发生后,从该顶点出发的各条弧所代表的活动才能开始
Ⅱ只有某顶点的各条弧代表的活动结束后,该顶点代表的事件才能发生
AOE网中有一个入度为0的点,称源点(起点),一个出度为0的点,称汇点(终点)
路径长度指完成这条路径上各个活动所需要的时间之和
完成整个工程的最短时间为从源点到汇点的最长路径长度,该路径称关键路径
事件Vj的最早发生时间Ve(j)
从源点起,Ve(j) = max {Ve(i) + dut(<i, j>)}
事件Vj的最迟发生时间Vl(j)
从汇点起,Vl(j) = min {Vl(i) - dut(<i, j>)}
活动ai的最早开始时间ee(i)
ai由弧<j,k>表示,ee(i) = Ve(j)
活动ai的最迟开始时间ee(i)
ai由弧<j,k>表示,el(i) = Vl(k) - dut(<j,k>)
关键活动:满足el(i) = ee(i)的ai
关键路径:由关键活动组成的路径
若AOE网中只有一条关键路径,则提高任意关键活动的速度可缩短工期
若AOE网中有多条条关键路径,则提高共同关键活动的速度可缩短工期
十一、查找
关键字:数据元素中某个数据项的值,可以用它识别一个数据元素
(1)静态查找表
对查找表的查找仅是以查询为目的,不改动查找表中的数据
①顺序表的查找
顺序查找:从表中的第一个或最后一个元素开始,逐个查找
将n个记录从1号单元开始连续存放,将待查找的关键字值记录放入0号单元,查找时从第n个记录开始,若返回0,则表示没查到
性能分析:
若不计查找不成功: A S L s s = n + 1 2 ASLss = \frac{n+1}{2} ASLss=2n+1
若计查找不成功: A S L s s = 3 ( n + 1 ) 4 ASLss = \frac{3(n+1)}{4} ASLss=43(n+1)
效率较低
int Search_Seq(SSTable ST, KeyType key) //在顺序表顺序查找关键字等于key的数据元素,若找到,返回该数据元素在顺序表中的顺序,否则返回0
{
strcpy(ST.elem[0].number, key); //将key赋值给零号单元的关键字,以便在查找不到key时返回0
int i;
for (i = ST.length, cnt = 1; strcmp(ST.elem[i].number, key) != 0; --i;
return i;
}
②有序表的查找
折半查找:先确定待查记录所在的范围,再逐步缩小该范围直到找到或找不到为止
查找成功和不成功最多比较 ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n\rfloor+1 ⌊log2n⌋+1次
性能分析:
A S L b s = l o g 2 ( n + 1 ) − 1 ASLbs = log_2(n + 1) - 1 ASLbs=log2(n+1)−1
int Search_Bin(SSTable ST, KeyType key) //在顺序表折半查找关键字等于key的数据元素,若找到,返回该数据元素在顺序表中的顺序,否则返回0
{
int low, mid, high;
low = 1;
high = ST.length;
while (low <= high) //当low>hight时说明找不到
{
mid = (low + high) / 2;
if (strcmp(ST.elem[mid].number, key) == 0) //找到待查元素
{
printf("half search is finsished! the search times is : %d\n", cnt);
return mid;
}
else if (strcmp(ST.elem[mid].number, key) > 0) //待查元素在前半区域
{
high = mid - 1;
}
else //待查元素在后半区域
{
low = mid + 1;
}
}
return 0; //找不到
}
③索引顺序表的查找
把线性表分成若干块,每一块中的元素存储顺序是任意的,前一块中的最大关键字小于后一块中的最小关键字值
需建立一个索引表,索引表中的一项对应线性表中的一块,索引项为:该块最大关键字,本块第一个结点指针
分块查找的步骤:先确定待查记录所在的块;然后在块中顺序查找
(2)动态查找表
在查找过程中同时伴随插入不存在的元素或删除某个已存在的元素
①二叉排序树:非空左子树的结点小于根结点,非空右子树的结点大于根结点
查找:
在二叉排序树T中查找key的过程为:
若T是空树则查,找失败
若key等于T的根结点的数据域之值,则查找成功
若key小于T的根结点的数据域之值,则搜索左子树,否则查找右子树
插入、删除:
②二叉平衡树:左右子树的深度不超过1
BF:平衡因子,左子树深度-右子树深度,平衡二叉树任何结点的BF=-1,1,0
在最好情况下:平均查找长度为 O ( l o g 2 n ) O(log_2n) O(log2n)
在最坏情况下:平均查找长度为 ( n + 1 ) 2 \frac{(n + 1)}{2} 2(n+1)
调整:
③B-树:m阶的B-树至多有m棵子树,若根结点不是叶子结点,在至少有两棵子树;除跟以外的所有非终结点至少有 ⌈ m 2 ⌉ \lceil\frac{m}{2}\rceil ⌈2m⌉棵子树,每个结点的关键字数大于等于 ⌈ m 2 ⌉ − 1 \lceil\frac{m}{2}\rceil-1 ⌈2m⌉−1,小于等于m-1
插入、删除:
④哈希表查找
通过关键字值进行某种运算(哈希函数),直接求出记录文件的地址
冲突:不同的关键字得到同一地址
处理冲突:
十二、内部排序
内部排序与外部排序
内部排序 | 排序数据全部放在内存中,不涉及外存的排序方法 |
外部排序 | 待排序的数据量很大,以至内存一次不能容纳全部数据。在排序过程中尚需要对外存进行访问的排序过程 |
稳定排序与不稳定排序
稳定排序 | 两个关键字值相同的记录经排序后的先后位置不变 |
不稳定排序 | 两个关键字值相同的记录经排序后的先后位置改变 |
内部排序的分类
按基本操作分 | |
---|---|
插入排序 | 直接插入排序 |
折半插入排序 | |
2-路插入排序 | |
表插入排序 | |
希尔排序 | |
交换排序 | 起泡排序 |
快速排序 | |
选择排序 | 简单选择排序 |
树形选择排序 | |
堆排序 | |
归并排序 | |
基数排序 |
(1)插入排序
每趟将一个待排序的记录按关键字值大小插入到已排序的子文件的适当位置,直到全部记录插入完成
①直接插入排序
缺点:每趟只能确定一个元素,表长为n时需要n-1趟
②其他插入排序
在直接插入的基础上,减少比较和移动的次数
Ⅰ折半插入排序
减少关键字间的比较次数,记录移动的次数不变
Ⅱ2路插入排序
以折半插入为基础,将第一个数看成处于中间位置的数,小的放它左边,大的放它右边,减少排序过程中移动记录的次数,需要n个记录的辅助空间
Ⅲ表插入排序
③希尔排序
将待排记录分成若干子序列分别进行直接插入排序,待整个序列基本有序再对全体嫉妒进行一次插入排序
(2)交换排序
①起泡排序
每次从n-1个记录中选一个最大的放在第n-1个位置上,最后一趟无进行任何交换记录
②快速排序
在待排记录中找一个当枢轴记录,把这个记录放在最终位置,将序列分成两部分,关键字比该记录小的放前面,大的放后面。对所有两部分进行该操作直到每部分只有一个记录。
特点:每趟不止确定一个元素的位置
(3)选择排序
①简单选择排序
通过n-i次关键字的比较,从n-i+1个记录中选出关键字最小的记录与第i个记录交换
②树形排序
先对n个记录的关键字进行两两比较,然后在 ⌈ n 2 ⌉ \lceil\frac{n}{2}\rceil ⌈2n⌉个较小者间进行两两比较,直到选出最小的关键字。该过程可用n个叶子结点的正则二叉树表示,正则二叉树的结点数为2n-1
③堆排序
将初始大根堆不断调整为最大堆
(4)归并排序
将两个或以上的有序表组合成一个新的有序表
(5)基数排序
先排最低位,再排次低位,直到最低位
排序方法 | 时间复杂度 | 稳定性 | 辅助存储 |
---|---|---|---|
直接插入 | O(n^2) | 稳定 | O(1) |
希尔排序 | O(nlogn) | 不稳定 | O(1) |
起泡排序 | O(n^2) | 稳定 | O(1) |
快速排序 | O(n^2) | 不稳定 | O(logn) |
直接选择 | O(n^2) | 稳定 | O(1) |
堆排序 | O(nlogn) | 不稳定 | O(1) |
归并 | O(nlogn) | 稳定 | O(n) |
基数 | O(d(n + rd)) | 稳定 | O(rd) |