C语言的指针
int* p = &a
解释为,*p说明p的类型是地址,实际上是p= &a,&是取地址符,指针与a的地址位置等同, *p = a *
C语言的结构体
写一个链式节点的结构体
typedef struct Node {
int data;
struct Node* next;
} Node;
链表
所有链式存储结构、链表、图等,都应先创建节点,以及新建节点的方法
//链式节点的结构体,数据域和指针域
typedef struct Node {
int data;
struct Node* next;
} Node;
// 创建新节点 的函数
Node* createNode(int data) { //定义函数的返回类型是一个节点
Node* newNode = (Node*)malloc(sizeof(Node)); //申请内存分配
if (!newNode) {
printf("内存分配失败\n"); exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
一般的链表操作都是传入链表不存储数据的head头指针
特殊的链表:
①循环链表,尾结点的next不指向null而指向head头结点实现闭环
②双向链表:除next指针指向后继,还有prior指针指向前驱
栈和队列
1、栈:分为顺序栈和链式栈,前者是基于数组的一种后进先出的存储结构,有事先规定好的大小,链式每个节点分配地址,理论可以无限
2、队列:头出尾进但是容易存在假溢出(尾到了极限,但是头部还有空间),便采用循环队列,判断队列满的条件是 front= (rear+1)%maxSize
串的模式匹配
简单匹配算法:指针i从主串开始遍历,指针j从模式串开始遍历(默认i,j的初始值都是1),匹配失败后,主串指针回溯到 i-j+的位置,模式串回溯到 j=1
KMP模式匹配算法:主串的指针不回溯,模式串的指针回溯到next[j],每个位置的next[j]计算方法为j=1时,next[j]=0,j=2时,next[j] = 1,再往后其他位置是最大前后缀相等的位数+1
数组和广义表
数组的内存是行序或者列序的连续地址
稀疏矩阵的存储方式是三元组或者十字链表,三元组M(a,b,c)a行,b列,c是值,十字链表就是每一行每一列都有一个头指针指向第一个不为零的元素,然后继续指向下一个不为零的,若是3*3矩阵,则有三个行指针,三个列指针
广义表 长度,就是最浅层的元素个数。深度就是嵌套的最深的层数,元素个数是所有层上的节点总和,例如(a,(b,(c,d))) 长度是2,深度是3,总个数是4
二叉树
** 性质**:n0 = n2+1 (适用于所有二叉树,即叶子节点是有两个孩子的父节点个数+1
几种特殊的二叉树:满二叉树:所有非叶子结点都有两个孩子
完全二叉树:按从左到右、从上到下的顺序从0开始标号,不允许存在空位,对于序号为i的节点,父节点是(i-1)/2 '/'是整除,左孩子是2i+1,右孩子是2i+2
二叉树存储结构,顺序存储结构和链式存储结构
顺序存储结构:即从上到下、从左往右开始编号,没有节点的位置存值为0,缺点容易浪费内存
链式存储结构:
写一个节点存储结构,包含数据,指向左孩子的指针,指向右孩子的节点,从根节点出发,通过指针串成链式结构,缺点是由于单向链表,难以通过孩子节点找父节点
一般还是链式存储二叉树
#include <stdio.h>
#include <stdlib.h>
// 定义节点结构体
typedef struct Node {
int value;
struct Node* left;
struct Node* right;
} Node;
// 创建新节点
Node* createNode(int value) { //定义函数的返回值是一个Node*结构体
Node* newNode = (Node*)malloc(sizeof(Node))// c语言的申请内存类似于java的new一个实例
newNode->value = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
// 定义二叉树结构体(虽然这里其实只需要一个根节点指针,但为了与Java结构一致,保留这个结构体)
typedef struct BinaryTree {
Node* root;
} BinaryTree;
// 创建二叉树
BinaryTree* createTree(int rootValue) {
BinaryTree* newTree = (BinaryTree*)malloc(sizeof(BinaryTree));
newTree->root = createNode(rootValue);
return newTree;
}
// 插入节点
Node* insertRec(Node* root, int value) {
if (root == NULL) {
return createNode(value);
}
if (value < root->value) {
root->left = insertRec(root->left, value);
} else {
root->right = insertRec(root->right, value);
}
return root;
}
// 在二叉树中插入节点
void insert(BinaryTree* tree, int value) {
tree->root = insertRec(tree->root, value);
}
// 中序遍历
void inorderRec(Node* root) {
if (root != NULL) {
inorderRec(root->left);
printf("%d ", root->value);
inorderRec(root->right);
}
}
// 输出中序遍历结果
void inorder(BinaryTree* tree) {
inorderRec(tree->root);
printf("\n");
}
// 前序遍历
void preorderRec(Node* root) {
if (root != NULL) {
printf("%d ", root->value);
preorderRec(root->left);
preorderRec(root->right);
}
}
// 输出前序遍历结果
void preorder(BinaryTree* tree) {
preorderRec(tree->root);
printf("\n");
}
// 后序遍历
void postorderRec(Node* root) {
if (root != NULL) {
postorderRec(root->left);
postorderRec(root->right);
printf("%d ", root->value);
}
}
// 输出后序遍历结果
void postorder(BinaryTree* tree) {
postorderRec(tree->root);
printf("\n");
}
int main() {
BinaryTree* tree = createTree(10); // 创建一个二叉树,根节点值为10
insert(tree, 5); // 插入节点5
insert(tree, 15); // 插入节点15
insert(tree, 3); // 插入节点3
insert(tree, 7); // 插入节点7
insert(tree, 12); // 插入节点12
insert(tree, 18); // 插入节点18
printf("中序遍历结果:\n");
inorder(tree); // 输出中序遍历结果
printf("前序遍历结果:\n");
preorder(tree); // 输出前序遍历结果
printf("后序遍历结果:\n");
postorder(tree); // 输出后序遍历结果
// 注意:这里没有释放内存的代码,实际应用中需要添加适当的内存释放函数来避免内存泄漏
return 0;
}
线索二叉树
一种类似双向链式结构的二叉树存储,指针lchilld和rchild在有左右孩子时指向左右孩子,无左右孩子指向在该遍历顺序的前驱或者后继。br> 所以共有2n个链域,其中n+1个为指向前驱或者后继的空链域,只有n-1个指向孩子节点。
树的普遍性存储结构
1、孩子链表示法:把所有存在节点编号,把节点的指针指向其所有孩子节点串成的串,形成孩子链。 2、带双亲的孩子链表示法:与前者几乎一致,唯一多了一个指针指向该节点的父节点 3、孩子兄弟表示法:转化成二叉树的结构,节点的左孩子是本节点左边数第一个孩子节点,节点的右孩子是节点的第一个兄弟节点
树、森林、二叉树的转换
方法就是孩子兄弟表示法
树->二叉树:孩子兄弟表示法 森林->二叉树:把森林中的每个树先转化成二叉树,再把每个转化好的二叉树的根节点当做兄弟节点,链接起来即可。
树的遍历
只有前序和后序,与二叉树的基本原理差不多
哈夫曼树
生成方法:最小元素加和再带入,使目标因子全在叶子结点上
树的最小带权路径:所有节点的权值*路径长的总和
哈夫曼编码:左0右1
二叉排序树
左子树上所有节点小于根节点,右子树上所有节点大于根节点。
优化:实现排序,且平均查找次数最小 ->平衡二叉树:每个节点的平衡因子(左子树深度-右子树深度的绝对值)小于等于1,构建的过程出现某节点不满足的情况,则以此节点为起点向新插入的节点的方向找三个节点(包含此节点是三个),再排序,然后生成的树替换掉根节点上的树,其他此树节点正常排序
图
基本两类:有向图和无向图
完全图:所有点与其他的点均有直接路径(有向图为来回两条路径)
,无向图路径条数 n(n-1)/2,有向图n(n-1)
图的表示方法:
1.邻接矩阵法:两点之间有路为1,无路为0,无向图邻接矩阵为对称矩阵,有向图:行为起点、列为终点
2.邻接表:每个点和其所有相邻点连成一条链,对于有向图:邻接表(出边表)、逆邻接表(入边表)
3.十字链表:把有向图的邻接表和逆邻接表结合起来
图的遍历
1.深度优先遍历(DFS):选定一个起点,开始遍历,直到无法遍历到未遍历的新节点,从已遍历的节点找一个有相邻节点未遍历的节点作为新起点,再进行以上遍历,直到遍历所有
2.广度优先遍历(BFS):选定一个起点,然后遍历其所有相邻节点,然后再遍历新节点的所有相邻节点,以此类推,直至遍历所有。
图(连通图)的最小生成树算法
1.普里姆算法(prim):选一个节点,纳入一个点集,然后找这个点集中所有不生成环的最小值相邻边,将这个相邻边的另一节点纳入点集,以此类推,直至纳入所有节点
2.克鲁斯卡尔算法(kruskal):从最小边开始,从小到大依次筛查各个边,生成环不选入,直至连通所有节点。
拓扑排序和关键路径
1.拓扑结构:有向无环图(不可以按方向回到起点) 入度为零的点叫源点,出度为零为汇点
-
拓扑排序:从源点开始,删除入度为0的点和其所有相邻边,然后重复以上操作,把删除的点按顺序排即为拓扑排序 ;
逆拓扑排序:从汇点开始,删除出度为零的点及其边,删除先后顺序即为逆拓扑排序 -
关键路径:从源点到汇点的最长路径,关键路径上的所有点都是关键事件,关键路径上的所有活动都是关键活动。
-
事件(点)的最早发生时间:源点到这个点的最大路径长
事件的最晚发生时间:源点和汇点的最晚时间与最早时间一致,从汇点一直倒着往回删除,让时间最小
关键路径上的事件的最早最晚时间相等 -
活动(边)的最早发生时间:边的起点的点的最早发生时间
活动的最晚发生时间:边的终点的点的最晚发生时间减去这个边的权值
关键路径上的活动的最早最晚时间相等有向图的最短路径
1.迪杰斯特拉(dijkstra)算法:从起点开始检查到起点距离最近且没有被选中的点,在计算到这个点到其他点是否会更新其他点的最小距离。
2。弗洛伊德算法:用矩阵表示,对角线是0,无路节点间直达的路记为正无穷,然后以p-1为基础,遍历到pn矩阵,n的含义是一定要经过这个点
哈希表
定义:将节点的关键字K为自变量,通过一个哈希函数,计算出对应的函数值作为该节点的地址,通过这个哈希函数的映射,实现通过节点关键字迅速查找到该节点。
哈希函数的特性:地址的唯一性质得,哈希函数生成的地址尽可能保持唯一性,所以应选择合适的函数作为哈希函数,①可以用H= Ak+b直接定址法,最能保持独一性,缺点是地址跨度大,浪费内存。②质数取余法 ③平方取中法
处理哈希表的冲突:有可能存在k1≠k2,但是H(K1)=H(k2),就会产生冲突
处理方法:1.开放定址法:①线性探测法:按顺序计算地址,地址冲突一个个往后找空位置,利用模运算实现循环(找到队尾找不到返回头开始找)
②二次探测法:若H(k)=d冲突,则依次寻找d+di位置,di = 1^2,-1^2,2^2,-2^2 ...... 找到空地址位置为止
3.伪随机序列法:提前规定一个关于H(k)=d,d+di中di的序列
4.再哈希法:定义多个哈希函数,一个冲突,按顺序采用下一个
2.拉链法:每个哈希地址存一个单链表,存放所有H(K)为此地址的节点,直接在该单链表中查找即可,缺点是可能需要更大的存储空间