赞
踩
常用时间复杂度
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
线性表:零个或多个数据元素的有限序列
线性表元素的个数n (n ≥ 0) 定义为线性表的长度,当 n = 0 时,称为空表。
这些是线性表基本操作
复杂操作由此衍生
/*将所有的在线性表Lb中但不在La中的数据元素插入到La*/
void union( List *La,List Lb) {
int La_len, Lb_len,i;
ElemType e; /*声明与La和Lb相同的数据元素e*/
La_len = ListLength(La);/*求线性表的长度*/
Lb_len = ListLength(Lb);
for (i=1; i<=Lb_len; i++) {
GetElem (Lb, i,e);/*取Lb中第i个数据元素赋给e*/
if ( !LocateElem( La,e,equal))/*La中不存在和e相同数据元素*/
ListInsert ( La, ++La_len, e);/*插入*/
}
}
#define MAXSIZE 20 /*存储空间初始分配量*/
typedef int ElemType; /*ElemType类型根据实际情况而定,这里假设为int*/
typedef struct {
ElemType data[MAXSIZE];/*数组存储数据元素,最大值为MAXSIZE*/
int length; /*线性表当前长度*/
}SqList;
基本操作
获取元素
插入
删除
注意
优点
缺点
/*线性表的单链表存储结构*/
typedef struct Node { //结点
ElemType data; //结点数据域
struct Node *next; //结点指针域,指向下一个结点
}Node;
typedef struct Node *LinkList; //LinkList为头指针
基本操作
声明一个结点 p 指向链表第一个结点,初始化 j 从 1 开始
当 j < i 时 (p && j < i) ,就遍历链表,让 p 的指针向后移动,不断指向下一结点,j 累加 1
若到链表末尾 p 为空 ( !p || j > i) , 则说明第 i 个元素不存在
否则查找成功,返回结点p的数据
插入
删除
ListNode* removeElements(ListNode* head, int val) { // 删除头结点 while (head != NULL && head->val == val) { // 注意这里不是if ListNode* tmp = head; head = head->next; delete tmp; } // 删除非头结点 ListNode* cur = head; while (cur != NULL && cur->next!= NULL) { if (cur->next->val == val) { ListNode* tmp = cur->next; cur->next = cur->next->next; delete tmp; } else { cur = cur->next; } } return head; }
注意
头结点
单链表插入和删除 p 和 p->next 问题
/*随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)*/
void CreateListHead(LinkList *L,int n) {
LinkList p;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; /*先建立一个带头结点的单链表*/
for(i = 0; i < n; i++) {
p = (LinkList)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
p->next = (*L)->next;
(*L)->next = p; /*插入到表头*/
}
}
/*随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)*/
void CreateListTail(LinkList *L,int n) {
LinkList p, r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L; //r为指向尾部的结点
for(i = 0; i < n; i++) {
p = (LinkList)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
r->next = p; //将表尾终端结点的指针指向新结点
r = p; //将当前的新结点定义为表尾终端结点
}
r->next = NULL; //表示当前链表结束
}
时间性能
空间性能
单链表 > 顺序
//线性表的双向链表存储结构
typedef struct DulNode {
ElemType data;
struct DulNode *prior; //直接前驱指针
struct DulNode *next; //直接后驱指针
}DulNode, *DuLinkList;
双向链表是单链表中扩展出来的结构,很多操作是和单链表相同的,比如求长度的ListLength,查找元素的GetElem,获得元素位置的LocateElem等。这些操作都只要涉及一个方向的指针即可,另一个指针并不能提供什么帮助
插入和删除需要更改两个指针变量(顺序不能乱)
s->prior = p; //把p赋值给s前驱
s->next = p->next; //把2p->next赋值给s的后继
p->next->prior = s; //把s赋值给p->next的前驱
p->next = s; //把s赋值给p的后继
p->prior->next = p->next; //把p->next赋值给p->prior的后继
p->next->prior = p->prior; //把p->prior赋值给p->next的前驱
free(p);
栈是限定仅在表尾进行插入和删除操作的线性表
typedef int SElemType;
typedef struct {
SElemType data[MAXSIZE];
int top; //用于栈顶指针,指向最顶层的元素
}SqStack;
进栈
出栈
代码实现
#include<stdio.h> #include<malloc.h> #define DataType int #define MAXSIZE 1024 struct SeqStack { DataType data[MAXSIZE]; int top; }; //栈初始化,成功返回栈对象指针,失败返回空指针NULL SeqStack* initSeqStack() { SeqStack* s=(SeqStack*)malloc(sizeof(SeqStack)); if(!s) { printf("空间不足\n"); return NULL; } else { s->top = -1; return s; } } //判断栈是否为空 bool isEmptySeqStack(SeqStack* s) { if (s->top == -1) return true; else return false; } //入栈,返回-1失败,0成功 int pushSeqStack(SeqStack* s, DataType x) { if(s->top == MAXSIZE-1) { return -1;//栈满不能入栈 } else { s->top++; s->data[s->top] = x; return 0; } } //出栈,返回-1失败,0成功 int popSeqStack(SeqStack* s, DataType* x) { if(isEmptySeqStack(s)) { return -1;//栈空不能出栈 } else { *x = s->data[s->top]; s->top--; return 0; } } //取栈顶元素,返回-1失败,0成功 int topSeqStack(SeqStack* s,DataType* x) { if (isEmptySeqStack(s)) return -1; //栈空 else { *x=s->data[s->top]; return 0; } } //打印栈中元素 int printSeqStack(SeqStack* s) { int i; printf("当前栈中的元素:\n"); for (i = s->top; i >= 0; i--) printf("%4d",s->data[i]); printf("\n"); return 0; } //test int main() { SeqStack* seqStack=initSeqStack(); if(seqStack) { //将4、5、7分别入栈 pushSeqStack(seqStack,4); pushSeqStack(seqStack,5); pushSeqStack(seqStack,7); //打印栈内所有元素 printSeqStack(seqStack); //获取栈顶元素 DataType x=0; int ret=topSeqStack(seqStack,&x); if(0==ret) { printf("top element is %d\n",x); } //将栈顶元素出栈 ret=popSeqStack(seqStack,&x); if(0==ret) { printf("pop top element is %d\n",x); } } return 0; }
typedef struct {
SElenType data[MAXSIZE];
int top1; //栈1栈顶指针
int top2; //栈2栈顶指针
}SqDoubleStack;
typedef struct StackNode { //链栈结点
SElemType data;
struct StackNode *next;
}StackNode/*链栈结点*/, *LinkStackPtr/*链栈头指针*/;
typedef struct LinkStack { //链栈
LinkStackPtr top; //链栈头指针
int count; //结点元素个数
}LinkStack;
基本操作(和单链表差不多,插入和删除有不同)
/*插入元素e为新的栈顶元素*/
Status Push ( LinkStack *S,SElemType e) {
LinkstackPtr s=(LinkStackPtr) malloc(sizeof (StackNode));
s->data=e;
s->next=S->top; /*把当前的栈顶元素赋值给新结点的直接后继*/
S->top=s; /*将新的结点s赋值给栈顶指针*/
S->count++;
return OK;
}
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop ( LinkStack *S,SElemType *e) {
LinkStackPtr p;
if(stackEmpty(*S))
return ERROR;
*e=S->top->data;
p=S->top; /*将栈顶结点赋值给p*/
S->top=S->top->next; /*使得栈顶指针下移一位,指向后一结点*/
free (p);
return OK;
}
注意
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表
顺序存储结构在队头弹出一个元素,其余元素往前移动,调整时间复杂度为O(n),很低效
出现front和rear指针,动态指向队列(通过指针的移动指定队列起止下标),调整时间复杂度为O(1),更高效
front指针指向队头元素,rear指针指向队尾元素的下一个位置。当front等于rear时,队列为空队列
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */
/*循环队列的顺序存储结构*/
typedef struct {
QElemType data [MAXSIZE];
int front; /*头指针*/
int rear; /*尾指针,若队列不空,指向队列尾元素的下一个位置*/
}SqQueue;
基本操作
Status InitQueue ( SqQueue *Q) {
Q->front = 0;
Q->rear = 0;
return OK;
}
/*若队列未满,则插入元素e为Q新的队尾元素*/
Status EnQueue (SqQueue *Q, QElemType e) {
if (Q->front = Q->rear) //队列空的判断
return ERROR;
Q->data[Q->rear]=e; /*将元素e赋值给队尾*/
0->rear= (Q->rear+1) 号MAXSIZE; /*rear指针向后移一位置,若到最后则转到数组头部*/
return OK;
}
/*若队列不空,则删除中队头元素,用e返回其值*/
Status DeQueue ( SqQueue *Q,QElemType *e) {
if (Q->front = Q->rear) //队列空的判断
return ERROR;
*e=Q->data[Q->front]; /*将队头元素赋值给e*/
Q->front=( Q->front+1)% MAXSIZE; /*front指针向后移一位置, 若到最后则转到数组头部*/
return OK;
}
typedef int QElemType;/* QElemType类型根据实际情况而定,这里假设为int */
typedef struct QNode { /*结点结构*/
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct { /*队列的链表结构*/
QueuePtr front, rear; /*队头、队尾指针*/
}LinkQueue;
基本操作
Status DeQueue(LinkQueue *Q,QElemType *e) {
QueuePtr p;
if(Q->front==Q->rear)
return ERROR;
p=Q->front->next;/*将欲删除的队头结点暂存给p*/
*e=p->data;
Q->front->next=p->next;/*将原队头结点后继p->next赋值给头结点后继*/
if(Q->rear==p)/*若队头是队尾,则删除后将rear指向头结点*/
Q->rear=Q->front;
free (p);
return OK;
}
注意
串是由零个或多个字符组成的有限序列,又名字符串
解释 这里的StrLength 指的是字符串长度。原文中索引0的位置存放的应该是字符串长度的数值。如果以0为字符串起点的话,应该是0 ≤ pos ≤ StrLength - len
Index算法实现(模式匹配算法)
//T为非空串。若主串S中第pos个字符之后存在与T相等的子串,则返回第一个这样的子串在S中的位置,否则返回0 int Index(String s, String T, int pos) { int n, m, i; String sub; if(pos > 0) { //如果pos不符合规范,抛出异常 n = StringLength(S); //得到主串S的长度 m = StringLength(T); //得到子串T的长度 i = pos; while(i <= n - m + 1) { SubString(sub, S, i, m); //取主串第i个位置,长度与T相等子串给sub if(StringCompare(sub, T) != 0) //如果两串不相等 i++; else return i; } } return 0; //若无子串与T相等,返回0 }
//T非空,1 ≤ pos ≤ StringLength(s) int Index(String S, String T, int pos) { int i = pos; //i用于主串S中当前位置下标,若pos不为1,则从pos位置开始匹配 int j = 1; //j用于子串T中当前位置下标值 while(i <= S[0] && j <= T[0]) { //若i小于S长度且j小于T的长时循环 if(S[i] == T[j]) { i++; j++; } else { i = i - j + 2; //i退回到上次匹配首位的下一位 j = 1; } } if(j > T[0]) return i - T[0]; else return 0; }
基本概念
前缀:包含首位字符但不包含末位字符的子串
后缀:包含末位字符但不包含首位字符的子串
next数组定义:当主串与模式串的某一字符不匹配的时候,模式串要回退的位置
next[j]:其值 = 第j位字符前面的j - 1位字符组成的子串的前后缀重合字符数 + 1
j : 1 2 3 4 5 6 7 8
P: a b a a b c a c
Next[j]: 0 1 1 2 2 3 1 2 //Next[1] 默认为 0
关键推演
代码实现
//next数组 void GetNext(char T[], int next[]) { //length为串ch的长度 next[1] = 0; int i = 1, j = 0; while(i < T[0]) { if(j == 0 || T[i] == T[j]) { i++; j++; next[i] = j; } else j = next[j]; } } //简便计算:从第j - 1个字符向左看,若有k个连字符与开头的k个连字符相等,则该值为k + 1 //nextval数组 void GetNextVal(char T[], int nextval[]) { //length为串ch的长度 nextval[1] = 0; int i = 1, j = 0; while(i < T[0]) { if(j == 0 || T[i] == T[j]) { i++; j++; if(T[i] != T[j]) { nextval[i] = j; } else { nextval[i] = nextval[j]; } } else j = nextval[j]; } } //简便计算:第j个字符与第next[j]个字符想比较,若不等则等于next[j],若相等则等于nextval[next[j]]; //KMP //返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0 int Index_KMP(String S, String T, int pos) { int i = pos; //从pos位置开始匹配 int j = 1; //用于子串T中当前位置下标值 int next[255]; GetNext(T, next); while(i <= S[0] && j <= T[0]) { //若 i 小于 S 的长度且 j 小于 T 的长度时,循环继续 if(j == 0 || S[i] == T[j]) { //若两字母相等则继续,与朴素算法增加了 j = 0 的判断 i++; j++; } else { j = next[j]; //j退回合适的位置,i值不变 } } if(j > T[0]) { return i - T[0]; } else return 0; }
.5
双亲表示法
代码实现
#define MAX_TREE_SIZE 100
typedef int TElemType; //树结点的数据类型
typedef struct PTNode { //结点结构
TElemType data; //结点数据
int parent; //双亲位置
}PTNode;
typedef struct { //树结构
PTNode nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //根的位置和结点数
}PTree;
拓展完善
可以根据是否需求,拓展长子域(firstchild)以及右兄弟域(rightsib),不存在都用-1来表示
孩子表示法
概念
根据孩子数的不同,每个结点可以由多个指针域(多重链表表示法)
把每个结点的孩子结点排列起来,以单链表作为存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中
代码实现
#define MAX_TREE_SIZE 100 typedef struct CTNode { //孩子结点 int child; struct CTNode *next; }*ChildPtr; //可以加双亲结点,兄弟结点 typedef struct { //表头 TElemType data; ChildPtr firstchild; //在这可以加双亲指针,兄弟指针 }CTBox; typedef struct { //树结构 CTBox nodes[MAX_TREE_SIZE]; //结点数组 int r, n; //根的位置和结点数 }CTree;
孩子兄弟表示法
代码实现
typedef struct CSNode {
ElemType data;
struct CSNode *firstchild, *rightsib;
}CSNode, *CSTree;
基本概念
定义:二叉树是n个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成
相关概念
五中形态
特殊二叉树
左斜树(只有左子树)
右斜树(只有右子树)
满二叉树(所有分支结点都存在左子树和右子树,同时所有叶子结点都在同一层)
完全二叉树(不一定满,但是层序遍历时,每个结点的位置值和满二叉树层序遍历每个结点位置值相同)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NtEP3bET-1680705112798)(picture_source/14.jpg)]
平衡二叉树
性质
在二叉树的第 i 层上至多有2i-1个结点(i ≥ 1)
深度为k的二叉树至多有2k-1个结点(k ≥ 1)
对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2 + 1
具有n个结点的完全二叉树的深度为[log2n] + 1([x]表示不大于x的最大整数)
如果对一棵有n个结点的完全二叉树(其深度为[log2n] + 1)的结点按层序编号(从第1层到第[log2n] + 1层,每层从左到右),对一结点i(1≤i≤n)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QdEDF69c-1680705112799)(picture_source/14.jpg)]
适用情况
完全二叉树
如果不是完全二叉树,就会浪费存储空间
代码实现
typedef struct BiTNode {
TElemType data; //结点数据
struct BiTNode * lchild, rchild; //左右孩子指针
}BiTNode, *BiTree;
//再加一个双亲指针域,称之为三叉链表
void CreateBiTNode(BiTree *T) {
TElemType ch;
scanf("%c", &ch);
if(ch == '#') {
*T = NULL;
}
else {
*T = (BiTree)malloc(sizeof(BiTNode));
if(!*T)
exit(OVERFLOW);
(*T)->data = ch; //生成根结点
CreateBiTNode(&(*T)->lchild); //构造左子树
CreateBiTNode(&(*T)->rchild); //构造右子树
}
}
定义
二叉树的遍历时指从根结点出发,按照某种次序一次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次
遍历方法
前序
void PreOrderTraverse(BiTree T) {
if(T == NULL)
return;
printf("%c", T->data); //显示结点数据,可以更改为其他对结点操作
PreOrderTraverse(T->lchild); //再先序遍历左子树
PreOrderTraverse(T->rchild); //再先序遍历右子树
}
//输出结果为:ABCGHCEIF
中序
void InOrderTraverse(BiTree T) {
if(T == NULL)
return;
InOrderTraverse(T->lchild); //中序遍历左子树
printf("%c", T->data); //显示结点数据,可以更改为其他对结点操作
InOrderTraverse(T->rchild); //最后中序遍历右子树
}
//输出结果为:GDHBAEICF
后序
void PostOrderTraverse(BiTree T) {
if(T == NULL)
return;
PostOrderTraverse(T->lchild); //先后序遍历左子树
PostOrderTraverse(T->rchild); //再后序遍历右子树
printf("%c", T->data); //显示结点数据,可以更改为其他对结点操作
}
//输出结果为:GHDBIEFCA
层序
遍历性质
定义
我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树
线索化
将有空指针域的rchild指向其前驱,lchild指向其后继,没有前驱和后继统统指向NULL。
对二叉树以某种次序遍历使其变成线索二叉树的过程称作是线索化。
线索化的过程就是在遍历的过程中修改空指针的过程
增设ltag和rtag
ltag | rtag | |
---|---|---|
0 | 左孩子 | 右孩子 |
1 | 前驱 | 后继 |
代码实现
//二叉树的二叉线索存储结构的定义 typedef enum {Link, Thread/*线索*/} PointerTag; //Link == 0 表示指向左右孩子指针,Thread == 1 表示指向前驱或后继的线索 typedef struct BiTreeNode { //二叉线索存储结构 TElemType struct data; struct BiThrNode *lchild, *rchild; //左右孩子指针 PointerTag LTag; PointerTag RTag; //左右标志 }BiThrNode, *BiThrTree; //中序遍历进行中序线索化 BiThrTree pre; //全局变量,始终指向刚刚访问过的结点 void InThreading(BiThrTree p) { if(p) { InThreading(p->lchild); //递归左子树线索化 if(!p->lchild) { //没有左孩子 p->LTag = Thread; //前驱线索 p->lchild = pre; //左孩子指针指向前驱 } if(!pre->rchild) { //前驱没有右孩子 pre->RTag = Thread; //后继线索 pre->rchild = p; //前驱右孩子指针指向后继(当前结点p) } pre = p; //保持pre指向p的前驱 InThreading(p->rchild); //递归右子树线索化 } } //插入头结点 //T指向头结点,头结点左链lchild指向根结点,头结点右链rchild指向中序遍历的最后一个结点。中序遍历二叉线索链表表示二叉树T bool InOrderTraverse_Thr(BiThrTree T/*头结点指针*/) { BiThrNode* p;//结点指针 p = T->lchild; //p 指向根结点 while(p != T) { //空树或遍历结束时,p == T while(p->LTag == Link) //当LTag == 0 时循环到中序序列第一个结点 p = p->lchild; printf("%c", p->data); //显示结点数据,可以更改为其他对结点操作 while(p->RTag == Thread && p->rchild != T) { p = p->rchild; printf("%c", p->data); } p = p->rchild; //p进至其右子树根 } return true; }
适用情况
二叉树需经常遍历或查找结点时需要某种遍历序列中的前驱和后继
树->二叉树
森林->二叉树
二叉树->树
二叉树->森林
树的遍历
森林遍历
定义
特殊的完全二叉树,满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆
小根堆/大根堆
根结点最大,为大根堆;根结点最小,为小根堆
赫夫曼树(哈夫曼堆)
基本概念
路径
从树中一个结点到另一个结点之间的分支构成两个结点之间的路径
路径长度
路径上的分支数目
树的路径长度
从树根到每一结点的路径长度之和
赫夫曼树(最优二叉树)
带权路径长度WPL最小的二叉树
代码实现
//最大堆实现 #define _CRT_SECURE_NO_WARNINGS #include <bits/stdc++.h> using namespace std; #define ll long long //堆模板 template<typename T> class CMyHeap { T* Heap; int size; int capacity; public: CMyHeap() { Heap = NULL; size = capacity = 0; }; ~CMyHeap() { clear(); } void addNode(int val); void deleteNode(); void initHeap(T*& arr, int n); void clear(); //特地写入这样一个函数是为了既可以自动结束类,也可以手动结束类 }; template<typename T> void CMyHeap<T>::clear() { if (Heap) delete[]Heap; Heap = NULL; //很重要,如果要多次调用这个,不NULL会导致上面if的判断成立,因空指针释放而报错 size = capacity = 0; } //插入 template<typename T> void CMyHeap<T>::addNode(int val) { //动态扩容 if (size == capacity) { capacity = capacity + ((capacity >> 1) > 1 ? (capacity >> 1) : 1); T* temp = new T[capacity]; if (!temp) { printf("开辟内存失败!\n"); return; } for (int i = 0; i < size; i++) { temp[i] = Heap[i]; } if (Heap) { delete[] Heap; Heap = NULL; } Heap = temp; } //上浮操作 Heap[size++] = val; //后++(size - 1)为数组尾部 int index = size - 1; T tempNum = Heap[index]; while (index > 0) { int parentIndex = (index - 1) >> 1;//公式 if (tempNum/*始终拿这个去判断*/ > Heap[parentIndex]) { Heap[index] = Heap[parentIndex]; index = parentIndex; //立flag } else break; } Heap[index] = tempNum; return; } //删除 template<typename T> void CMyHeap<T>::deleteNode() { if (size == 0) { printf("堆空!\n"); return; } if(size > 1) //可以不写,没必要 Heap[0] = Heap[size - 1]; size--; int index = 0; int tempNum = Heap[index]; while (1) { int left = 2 * index + 1; int right = 2 * index + 2; bool isLeft = true; //初始化左边上浮 if (left > size - 1) break; else { if (right <= size - 1) { if (Heap[left] < Heap[right]) {//左边小于右边,右边上浮 isLeft = false; } } if (isLeft) { if (tempNum < Heap[left]) { Heap[index] = Heap[left]; index = left; } else break; } else { if (tempNum < Heap[right]) { Heap[index] = Heap[right]; index = right; } else break; } } } Heap[index] = tempNum; } //初始化 template<typename T> void CMyHeap<T>::initHeap(T*& arr, int n) { Heap = new T[n]; if (!Heap) { printf("空间分配失败!\n"); return; } size = capacity = n; int i; for (i = 0; i < size; i++) { Heap[i] = arr[i]; } //(size - 2) >> 1为最后一个有子结点的结点 for (i = (size - 2) >> 1; i >= 0; i--) { int index = i; //不能将index替换成i来--,因为index在下面的操作中会变 T tempNum = Heap[index]; while (1) { int l = 2 * index + 1; int r = 2 * index + 2; bool isL = true; if (l > size - 1) break; else { if (r <= size - 1) { if (Heap[l] < Heap[r]) { isL = false; } } if (isL) { if (tempNum < Heap[l]) { Heap[index] = Heap[l]; index = l; } else break; } else { if (tempNum < Heap[r]) { Heap[index] = Heap[r]; index = r; } else break; } } } Heap[index] = tempNum; } } int main() { int arr[] = { 5,7,23,7,3,65,4,22,30,1,0,55,37,64,38 }; int* pArr = arr; CMyHeap<int> cmh; cmh.initHeap(pArr, 15); cmh.addNode(39); cmh.deleteNode(); cmh.clear(); return 0; }
图:是由定点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合
有向图:弧有方向,顶点集和弧集构成的图
无向图:弧没方向
有向网,无向网:弧和边带权的图
完全网:n个定点,e条边。e=n(n - 1)/2条边的无向图
有向完全图……
稀疏图:e<nlogn;否则稠密图
邻接点:两个顶点之间存在一条边则两个点为邻接点
度:一个顶点有多少条关联边(出度 + 入度)
出度:从顶点发出的关联边的条数
入度:指向顶点的关联边的条数
简单路径:序列中顶点不重复出现的路径
回路:第一个顶点和最后一个顶点相同的路径
路径长度:路径上边的数目
连通图:任意两个顶点之间都有路径(不是都有边)
非连通图
连通分量:若无向图为非连通图,则各个极大联通子图称为图的连通分量
强连通图:任意两个顶点之间都存在一条有向路径,此图为强连通图
强连通分量
生成树
无向图的邻接矩阵
设G是有n个顶点的无向图,则G的邻接矩阵是一个n*n矩阵
A[i, j] = A[j, i] = 1 (Vi与Vj有关系)
邻接矩阵第i行(或第i列)的元素之和则是顶点Vi的度
有向图的邻接矩阵
邻接矩阵第i行的元素之和为顶点Vi的出度
邻接矩阵第j列的元素之和为顶点Vj的入度
代码实现
typedef char VertexType; //顶点类型应由用户定义 typedef int EdgeType; //边上的权值类型应由用户定义 #define MAXVEX 100 //最大顶点数,应由用户定义 #define INFINTY 65535 //用65535来代表∞ typedef struct { VertexType vexs[MAXVEX]; //顶点表 EdgeType arc[MAXVEX][MAXVEX]; //邻接矩阵,边表 int numVertexes, numEdges; //图中当前的顶点数和边数 }MGraph; //建立无向网图的邻接矩阵表示 void CreateMGraph(MGraph *G) { int i, j, k, w; scanf("%d%d", &G->numVertexes, &G->numEdges); //输入顶点数和边数 for (i = 0; i < G->numVertexes; i++) { scanf("%d", &G->vexs[i]); //读入顶点信息,建立顶点表 } for (i = 0; i < G->numVertexes; i++) { for (j = 0; j < G->numVertexes; j++) { G->arc[i][j] = INFINTY; //将arc矩阵无关项开到最大 } } for (k = 0; k < G->numEdges; k++) { scanf("%d%d%d", &i, &j, &w); G->arc[i][j] = w; G->arc[j][i] = G->arc[i][j]; //无向图,矩阵对称 } } //时间复杂度:O(n + n ^ 2 + e)
缺陷
存储空间浪费得比较多
特点:数组与链表相结合
类似于树存储结构里面的孩子表示法
无向图
有向图(邻接表便于分析出度,逆邻接表便于分析入度)
带权有向图
代码实现
typedef char vertexType;/*顶点类型应由用户定义*/ typedef int EdgeType;/*边上的权值类型应由用户定义*/ typedef struct EdgeNode { /*边表结点*/ int adjvex; /*邻接点域,存储该顶点对应的下标*/ EdgeType weight; /*用于存储权值,对于非网图可以不需要*/ struct EdgeNode *next;/*链域,指向下一个邻接点*/ }EdgeNode; typedef struct vertexNode {/*顶点表结点*/ vertexType data;/*顶点域,存储顶点信息*/ EdgeNode* firstedge;/*边表头指针*/ }VertexNode, AdjList[MAXVEX]; typedef struct { AdjList adjList; int numvertexes, numEdges; /*图中当前顶点数和边数*/ }GraphAdjList; //建立图的邻接表结构 void CreateALGraph(GraphAdjList* G) { int i,j,k; EdgeNode *e; printf("输入顶点鼓和边数:\n" ) ; scanf ( "%d%d" , &G->numvertexes,&G->numEdges );/*输入顶点数和边数*/ for(i = 0;i < G->numvertexes; i++) {/*读入顶点信息,建立顶点表*/ scanf("%d", &G->adjList[i].data);/*输入顶点信息*/ G->adjList[i].firstedge = NULL;/*将边表置为空表*/ for(k = 0;k < G->numEdges; k++) {/*建立边表*/ printf("输入边(vi, vj)上的顶点序号:n"); scanf("%d, %d", &i, &j);/*输入边(Vi, vj)上的顶点序号*/ e = (EdgeNode *)malloc(sizeof(EdgeNode));/*向内存申请空间, 生成边表结点*/ e->adjvex = j; /*邻接序号为j*/ e->next = G->adjList[i].firstedge;/*将e指针指向当前顶点指向的结点*/ G->adjList[i].firstedge = e;/*将当前顶点的指针指向e*/ e = (EdgeNode *)malloc(sizeof(EdgeNode));/*向内存申请空间, 生成边表结点*/ e->adjvex = i;/*邻接序号为i*/ e->next = G->adjList[j].firstedge;/*将e指针指向当前顶点指向的结点*/ G->adjList[j].firstedge = e;/*将当前顶点的指针指向e*/ } } }
将邻接表和逆邻接表结合起来
//边表结构体 typedef struct edgeNode { int tailvex; //弧尾顶点(入度),即记录到弧尾顶点的结点的下标 int headvex; //弧头顶点(出度),即记录从弧头结点到某结点,某结点的下标 EdgeNode* headlink; //入边表指针域,指向终点相同的下一条边 EdgeNode* taillink; //出边表指针域,指向起点相同的下一条边 int weight; //权值 }EdgeNode; //顶点表结构体 typedef struct vertexNode { int data; EdgeNode* firstin; //入边表头指针,指向该顶点的入边表中第一个结点 EdgeNode* firstout; //出边表头指针,指向该顶点的出边表中第一个结点 }VertexNode;
原理
从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到。
若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
数据结构
代码实现
原理
首先将图中每个顶点的访问标志设为 FALSE, 之后从图中某个顶点v0出发,访问此顶点,然后依次从v0的未被访问的邻接点出发深度优先遍历图,直至图中所有和v0有路径相通的顶点都被访问到为止;
若此时图中尚有顶点未被访问,则另选图中一个未曾访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
代码实现
prim算法
重点
按顶点构建生成树
代码实现
//邻接矩阵实现prim算法 #include <iostream> #define Max 103 using namespace std; typedef struct AMGraph { //图的结构体 int ves[Max]; //顶点表 int arcs[Max][Max]; //邻接矩阵 int vexnum, arcnum; //顶点数和边数 }; typedef struct Gedge { int adjvex; //最小边的前驱点 int lowcost; //最小边的权值 }; Gedge closedge[Max]; int sum = 0; //最小生成树权值和 void putin(AMGraph& G) //初始化并输入图 { int u, v, w; cin >> G.vexnum >> G.arcnum; for (int i = 1; i <= G.vexnum; i++) //初始化邻接矩阵值 { for (int j = 1; j <= G.vexnum; j++) G.arcs[i][j] = 0xcffffff; } for (int i = 1; i <= G.arcnum; i++) //输入边和其权值 { cin >> u >> v >> w; G.arcs[u][v] = G.arcs[v][u] = w; } } void Prim(AMGraph& G) //普利姆算法 { for (int i = 1; i <= G.vexnum; i++) //起始点默认为1 if (i != 1) closedge[i] = { 1, G.arcs[1][i] }; closedge[1].lowcost = 0; //初始化最小边集合 for (int i = 2; i <= G.vexnum; i++) //遍历n-1次 { int k, min = 0xcffffff; for (int j = 1; j <= G.vexnum; j++) //找到当前的最小边的顶点 { if (closedge[j].lowcost < min && closedge[j].lowcost != 0) { min = closedge[j].lowcost; k = j; } } cout << closedge[k].adjvex << " " << k << endl; sum += closedge[k].lowcost; //把最小边的权值求和 closedge[k].lowcost = 0; //把找到的最小边并入集合 for (int j = 1; j <= G.vexnum; j++) //刷新当前未并入集合的边 { if (G.arcs[k][j] < closedge[j].lowcost) { closedge[j].adjvex = k; closedge[j].lowcost = G.arcs[k][j]; } } } } int main() { AMGraph G; putin(G); Prim(G); cout << sum << endl; //输出要求的最小生成树的权值和 return 0; }
特点
适用于稠密图(边多),时间复杂度为O(V2)
kruskal算法
重点
按边构建生成树
代码实现
#include <iostream> #include <vector> #include <algorithm> #define Inf 0x7fffffff using namespace std; struct node { int u, v; int w; node(int a, int b, int x) :u(a), v(b), w(x) {} }; vector<node> edge; int vn, an; int pa[1000]; //比较两条边的权值 bool cmp(const node& a, const node& b) { return a.w < b.w; } //并查集分类 int find(int x) { return x == pa[x] ? x : pa[x] = find(pa[x]); } int kruskal() { //sumw 路径总权值 int sumw = 0, cnt = 0; for (int i = 1; i <= vn; ++i) pa[i] = i; for (int i = 0; i < an; ++i) { int x = find(edge[i].u), y = find(edge[i].v); if (x != y) { pa[x] = y; sumw += edge[i].w; ++cnt; } if (cnt == vn - 1) break; } return sumw; } int main() { cin >> vn >> an; for (int i = 0; i < an; ++i) { int a, b, x; cin >> a >> b >> x; edge.push_back(node(a, b, x)); } sort(edge.begin(), edge.end(), cmp); cout << kruskal(); return 0; }
特点
适用于稀疏图(很少),时间复杂度为O(ElogE)
理论
要求
代码实现
这个代码的最终效果是求得源点到goal点的最短路径距离,如果要写出最短路径的话,需要为每一个结点加一个前一结点的值。比如:将做一个结构体
struct Node{
int dis;
int prior;//在每一个for(int j)的时候可以赋值p
}node[maxn];
/*数据区*/
const int inf=0x3f3f3f3f; //代表无穷大。
const int maxn=100;//最大顶点数
int n,m;//n个顶点,m条边。
int start,goal;//起点与目标点。
int graph[maxn][maxn];//带权图
struct {
bool visited;//判断是否确定到源点的最终最短距离。
int dis;//顶点到源点的最短距离。
int prior;//前驱点
}Point[maxn];
/*初始化*/
void init(){
for (int i = 0; i < n; i++){
vis[i] = false;
dis[i] = graph[start][i];//初始化dis数组。
if (dis[i] < inf) {
prior[i] = start;
}
else {
prior[i] = -1;
}
}
vis[start] = true;
dis[start] = 0;
}
/*dijkstra*/ void dijkstra(){ //源点为源点start。 int minn;//记录每趟最短路径中最小的路径值。 int pos;//记录得到的minn所对应的下标。 init();//调用初始化函数。 for (int i = 1; i < n; i++){ //剩下n - 1个结点判断 //将n个顶点依次加入判断。 minn = inf; for (int j = 0; j < n; j++){ if (!visited[j] && dis[j] < minn) { minn = dis[j]; pos = j; } } //经过这趟for循环后我们找到的就是我们想要的点,可以确定这点到源点的最终最短距离了。 visited[pos] = true;//我们将此点并入已知集合。 //接下来就是更新dis数组了,也就是当前最短距离,这都是针对还没有并入已知集合的点。 for (int j = 0; j < n; j++) { if (!visited[j] && dis[j] > dis[pos] + graph[pos][j]) { dis[j] = dis[pos] + graph[pos][j]; prior[j] = pos; } } } //退出循环后,所有的点都已并入已知集合中,得到的dis数组也就是最终最短距离了。 cout << dis[goal] << endl;//输出目标点到源点的最短路径长度。 }
#include<bits/stdc++.h> using namespace std; int main() { while(cin>>n>>m){ memset(graph,inf,sizeof(graph)); int u,v,w; for (int i = 0; i < m; i++){ cin >> u >> v >> w; //graph[u][v]=w;//有向图 graph[u][v] = graph[v][u] = w;//无向图 } cin >> start >> goal;//输入起点与终点。 dijkstra(); } return 0; }
#include<bits/stdc++.h> // 万能头 using namespace std; #define INF 0x3f3f3f3f const int graphSize = 3; int floydGraph[graphSize][graphSize]; void init() { int n; memset(floydGraph, INF, sizeof(floydGraph)); cout << "请问有多少条边" << endl; cin >> n; int u, v, w; for (int i = 0; i < graphSize; i++) { floydGraph[i][i] = 0; } for (int i = 0; i < n; i++) { cin >> u >> v >> w; floydGraph[u][v] = w; } } void Floyd() { int i, j, k; for (k = 0; k < graphSize; k++) { for (i = 0; i < graphSize; i++) { for (j = 0; j < graphSize; j++) { if (floydGraph[i][k] + floydGraph[k][j] < floydGraph[i][j]) { floydGraph[i][j] = floydGraph[i][k] + floydGraph[k][j]; } } } } for (i = 0; i < graphSize; i++) { for (j = 0; j < graphSize - 1; j++) { cout << floydGraph[i][j] << '\t'; } cout << floydGraph[i][j] << endl; } } int main() { init(); Floyd(); system("pause"); return 0; }
声明格式:数据类型 变量名称[行数] [列数]
int num[5][8];
n维数组:若n-1维数组中的元素又是一个一维数组结构,则称作n维数组。
InitArray (&A,n, bound1, ...boundn) //构造数组A
DestroyArray (&A) //销毁数组A
Value (A, &e,index1,...,indexn) //取数组元素值
Assign (A, &e,index1,...,indexn) //给数组元素赋值
前提:
数组可以是多维的,但存储数据元素的内存单元地址是一维的, 因此,在存储数组结构之前,需要解决将多维关系映射到一维关系的问题。
顺序存储方式:
①以行序为主序(低下标优先)②以列序为主序(高下标优先)
以行序为主序:设数组开始存储位置LOC( 0, 0 ),存储每个元素需要L个存储单元则数组元素a[i] [j]的存储位置是: LOC( i, j)= LOC(0,0)+(n * i + j ) * L。(n为每一行所含有的元素个数,n * i + j表示在a[i] [j]前面所有元素个数)。
特殊矩阵的压缩存储
压缩存储的定义:若多个数据元素的值都相同,则只分配一个元素值的存储空间,且零元素不占存储空间。
能够压缩的矩阵:一些特殊矩阵,如:对称矩阵,对角矩阵,三角矩阵,稀疏矩阵等。
三角矩阵压缩存储:
LOC(i, j) = LOC(0,0) + (i * (i + 1) / 2 + j) * L;
稀疏矩阵:矩阵中非零元素的个数较少(一般小于5%)。
顺序存储
三元组
行逻辑链接的顺序表
带辅助行向量的二元组
伪地址表示法
链接存储
带行指针向量的单链表示法
行列表示法(十字链表)
多练表示法(正交表)
散列存储
压缩存储的原则:存各非零元的值、行列位置和矩阵的行列数。
稀疏矩阵的顺序存储方法——三元组存储
代码实现
//矩阵M为原矩阵,矩阵N为转置矩阵 typedef struct { int i, j; //该非零元的行下标和列下标 elemtype v; //该非零元的值 }Triple; //三元组类型 typedef union { Triple data[MAXSIZE + 1]; //非零元三元组表,data[0]未用 int mu, nu, tu; //矩阵的行数、列数和非零元个数 }TSMatrix; //稀疏矩阵类型 num[col]; //(伪代码)表示矩阵M中第col列中非零元的个数 cpot[col]; //(伪代码)表示矩阵M中第col列中第一个非零元在N.data中的恰当位置 //快速转置 //第一行和第一列同一从1开始计数 Status FastTransmat(TSMatrix M, TSMatrix& N) { N.mu = M.nu; N.nu = M.mu; N.tu = M.tu; if (M.tu != 0) { for (col = 1; col <= M.nu; col++) { num[col] = 0; //初始化num向量 } for (t = 1; t <= M.tu; t++) { num[M.data[t].j]++; //求M中每一列含非零元个数 } cpot[1] = 1; for (col = 2; col <= M.nu; col++) { cpot[col] = cpot[col - 1] + num[col - 1]; } //求M中每一列的第一个非零元在N.data中的序号(位置) for (p = 1; p <= M.tu; p++) { col = M.data[p].j; q = cpot[col]; N.data[q].j = M.data[p].i; //p为M中的行号 N.data[q].i = M.data[p].j; //q为转置后在N中的行号 N.data[q].v = M.data[p].v; cpot[col]++; //该列下次开始遍历的位置++ } } }
三元组(i,j,Aij)可以唯一确定矩阵的一个非零元素。
三元组顺序表的优点:非零元在表中按行序有序存储,因此便于进行依行顺序处理的矩阵运算。
三元组顺序表的缺点:不能随机存取,若按行号存取某一行中的非零元,则需从头开始进行查找。
稀疏矩阵的链式存储方法——十字链表
优点:它能够灵活地插入因运算而产生的新的非零元素,删除因运算而产生的新的零元素,实现矩阵的各种运算。
在十字链表中,矩阵的每一个非零元素用一个结点表示,该结点除了(row, col, value) 以外,还要有两个域:
①right:用于链接同一行中的下一个非零元素;
②down:用以链接同一列中的下一个非零元素。
也称列表,是n个单元素或子表所组成的有限序列
LS为广义表的名称,n为其长度
大写字母表示广义表的名称,小写字母表示原子
LS非空时,第一个元素为LS的表头,其余元素组成的表为表尾
广义表的长度为最外层包含元素个数
广义表的深度定义为所含括弧的重数
原子深度为0,空表深度为1
广义表可以共享,可以递归
表头表尾分析法
优点:给列表的运输算带来方便,如求列表的长度和深度,求表头、表尾等
缺点:表结点个数多,和列表中的括弧对数不匹配,也多占存储空间
李氏口诀!!!(谨记)(图推表的公式)
同层即并列,原子上表直接写,表上表加层括号,空表加空括号,最后加层打括号
子表分析法
概念
静态查找表
动态查找表
二叉排序树(二叉查找树、二叉搜索树)
typedef int dataType; typedef struct node { dataType data; struct node* lchild, * rchild; }BSTNode, *BSTree; void insertBST(BSTree& bst, dataType x) { if (bst == NULL) { bst = new BSTNode; bst->data = x; bst->lchild = NULL; bst->rchild = NULL; } else if (bst->data > x) { insertBST(bst->rchild, x); } else if (bst->data < x) { insertBST(bst->lchild, x); } } BSTNode* searchBST(BSTree& bst, dataType k) { if (bst == NULL) return NULL; else if (bst->data == k) return bst; else if (bst->data > k) searchBST(bst->lchild, k); else searchBST(bst->rchild, k); } bool delBSTNode(BSTree& bst, dataType k) { if (bst == NULL) return false; //如果找到了NULL,说明没有符合删除要求的结点 else if (bst->data == k) { if (bst->lchild == NULL) { //左子树为空,选取右子树最上层的元素(即大于k的最小值) BSTNode* p = bst; bst = p->rchild; delete p; } else { BSTNode* f = bst, * p = bst->lchild; while (p->rchild) { f = p; p = p->rchild; } bst->data = p->data; if (f == bst) bst->lchild = p->lchild; else f->rchild = p->lchild; delete p; } } else if (bst->data > k) { delBSTNode(bst->lchild, k); } else { delBSTNode(bst->rchild, k); } return true; } void inOrder(BSTree& T) { if (T == NULL) return; inOrder(T->lchild); cout << T->data << " "; inOrder(T->rchild); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。