最近一直很忙,从接大活到准备实习面试到速通期末,一直没有时间发博客,发个复习期末写的数据结构水一篇:)
逻辑结构和存储结构
- 逻辑结构:
- 1.线性:线性表,栈,队列,双队列,数组,串
- 2.非线性:二维数组,多维数组,广义表,树
- 存储结构:顺序存储(顺序表,顺序队列),链式存储(链表,链队列)
- 计算第i个元素存储位置:
l(ai)=l(a1)+(i-1)*l
- 行序二维数组的存储位置计算:
l(A[i][j])=l(A)+(i⋅n+j)⋅l
顺序表
// 初始化
void InitList(SqList* l){
for(int i = 0 ; i < MAXSIZE ; i++){
l->data[i] = 0;
}
l -> length = 0;
}
// 打印
void print(SqList L)
{
int i = 0;
for (i = 0;i < L.length;i++)
printf("%d ", L.data[i]);
printf("\n");
}
// 插入
Status Insert(SqList* l,int i,EleType data){
if(l->length + 1 < i || i<1 ){
return ERROR;
}
for(int j = l->length ; j >= i-1 ; j--){
l -> data[j] = l -> data[j-1];
}
l -> data[i-1] = data;
l -> length++;
return OK;
}
链表
- 尾插要考虑2种:
- 如果链表为空,则直接插入即可。
- 如果链表不为空,则需要找到尾结点再插入。
- 尾删要考虑3种:
- 链表为空。
- 链表中只有一个结点。
- 链表中有一个以上结点。
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef struct Node
{
int data;
struct Node* next;
}Node;
// 创建新节点
Node* BuyNode(int data){
Node* newnode = (Node*)malloc(sizeof(Node));
newnode -> next = NULL;
newnode -> data = data;
return newnode;
}
// 遍历打印
void printList(Node* head){
Node* cur = head;
while (cur)
{
printf("%d-> ",cur -> data);
cur = cur -> next;
}
printf("^\n");
}
// 尾插
void insertRear(Node** head,int data){
Node* newnode = BuyNode(data);
if(!*head){
*head = newnode;
}else{
Node* cur = *head;
while (cur -> next)
{
cur = cur -> next;
}
cur -> next = newnode;
}
}
// 头插
void insertHead(Node** head,Node* CurrentHead,int data){
Node* newnode = BuyNode(data);
if(!*head){
*head = newnode;
}else{
newnode -> next = CurrentHead;
*head = newnode;
}
}
// 插入
void insert(Node** head,Node* pos,int data){
Node* newnode= BuyNode(data);
if(*head == pos){
newnode -> next = *head;
*head = newnode;
}else{
Node* cur = *head;
while (cur->next != pos)
{
cur = cur -> next;
}
cur -> next = newnode;
newnode -> next = pos;
}
}
// 删除
void Delete(Node** head,Node* pos){
if(*head = pos){
*head = (*head) -> next;
}else{
Node* cur = *head;
while (cur -> next != pos)
{
cur = cur->next;
}
cur -> next = pos -> next;
free(pos);
pos=NULL;
}
}
// 查找
Node* find(Node* head, int i)
{
while (head)
{
if (head->data == i)
{
return head;
}
head = head->next;
}
return NULL;
}
int main(){
Node* head = NULL;
insertHead(&head,head,12);
insertHead(&head,head,33);
insertHead(&head,head,4);
insert(&head,find(head,33),97);
printList(head);
return 0;
}
循环链表
终端节点->next = 头节点。
判断是否为空:空循环链表的头节点->next=头节点(头节点->next->next->next都=头节点),
循环条件:cur->next != head
队列
循环队列
- 运算:
- 长度:(rear - front + MAXSIZE) % MAXSIZE
- 判空:rear == front
- 判满:(rear + 1) % MAXSIZE == front
- 入队:rear = (rear + 1) % MAXSIZE
- 出队:front = (front + 1) % MAXSIZE
- 遍历:cur = (cur + 1) % MAXSIZE
在这个例子中,rear指向的数据始终为空,来区分队空和队满
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 4
typedef struct
{
int front;
int rear;
int data[MAXSIZE];
}Sequece;
// 判满
int isFull(Sequece* Seq){
if(Seq -> front == (Seq ->rear + 1) % MAXSIZE){
return 1;
}else{
return 0;
}
}
// 判空
int isNull(Sequece* Seq){
if(Seq -> rear == Seq -> front){
return 1;
}else{
return 0;
}
}
// 遍历
void displaySequeue(Sequece* Seq) {
int length = (Seq -> rear - Seq -> front + MAXSIZE) % MAXSIZE;
printf("队列长度为%d,front = %d,rear = %d: " , length , Seq -> front , Seq -> rear);
int cur = Seq -> front;
while (cur != Seq -> rear)
{
printf("%d ",Seq -> data[cur]);
cur = (cur + 1) % MAXSIZE;
}
printf("\n");
}
// 初始化
void init(Sequece** Seq){
*Seq = (Sequece*)malloc(sizeof(Sequece));
(*Seq) -> front = 0;
(*Seq) -> rear = 0;
displaySequeue(*Seq);
}
//入队
void Enter(Sequece* Seq , int data){
if(isFull(Seq)){
printf("队列已满无法入队。");
}else{
Seq -> data[Seq -> rear] = data;
Seq -> rear = (Seq -> rear +1) % MAXSIZE;
}
displaySequeue(Seq);
}
// 出队
void Delete(Sequece* Seq){
if(isNull(Seq)){
printf("队列为空无法出队");
}else{
Seq -> front = (Seq -> front + 1) % MAXSIZE;
}
displaySequeue(Seq);
}
链队列
// 链式结构:表示队列
typedef struct QListNode
{
struct QListNode* _next;
int _data;
}QueueNode;
// 队列的结构
typedef struct Queue
{
QueueNode* _front;
QueueNode* _rear;
}Queue;
QueueNode * BuyQueueNode(int x)
{
QueueNode * cur = (QueueNode *)malloc(sizeof(QueueNode));
cur->_data = x;
cur->_next = NULL;
return cur;
}
// 初始化
void QueueInit(Queue* q)
{
q->_front = NULL;
q->_rear = NULL;
}
// 入队
void QueuePush(Queue* q, int x) //队列尾部入数据
{
QueueNode * cur = BuyQueueNode(x); //先把创建好的节点传过来
if (q->_front == NULL) //若是队列本身为空,队列里就只有这一个节点,又为队列头又为队列尾
{
q->_front = q->_rear = cur;
}
else
{
q->_rear->_next = cur; //否则,链表尾插操作
q->_rear = cur;
}
}
void QueuePop(Queue* q) //队列头部出数据
{
if (q->_front == NULL) //本身队列为空,不做操作
{
return;
}
QueueNode* tmp = q->_front->_next; //先保留下一个节点,防止断链
free(q->_front);
q->_front = tmp; //更新队列头部
}
栈
顺序栈
判空:top=-1
入栈:
S->top++; //栈顶指针增加一
S->data[S->top] = e;
出栈:
S->top--; //栈顶指针减一
链栈
判空:S->top=NULL
入栈:
// 入p节点
p->data = e;
p->next = S->top; //把当前的栈顶元素赋值给新节点的直接后继
S->top = p; //将新的结点S赋值给栈顶指针
出栈:
p = S->top; //将栈顶结点赋值给p
S->top = S->top->next; //使得栈顶指针下移一位,指向后一结点
free(p);
广义表
- 定义:又称列表,也是一种线性存储结构,既可以存储不可再分的元素,也可以存储广义表,如广义表 LS = {1,{1,2,3}}
- 原子:如LS的1
- 子表:如LS的{1,2,3}
- 长度:原子+子表个数,如 {1,{1,2,3}} 长度为2
- 深度:括号个数,如 {1,{1,2,3,{1,2}}} 深度为3
- 表头和表尾:当广义表不是空表时,称第一个数据(原子或子表)为"表头",剩下的数据构成的新广义表为"表尾"。
- 除非广义表为空表,否则广义表一定具有表头和表尾,且广义表的表尾一定是一个广义表。
- 如(a,b)表头:a,表尾:(b)
- 结构定义
typedef struct GNode {
int tag; // 标志域, 0表示原子, 1表示子表
union {
int atom; // 原子结点的值域
struct GNode* hp; // 子表结点的指针域, hp指向表头
} subNode;
struct GNode* tp; // 指向下一个数据元素
} GLNode, *Glist;
如('a', ('b', 'c'), 'd'):
树
二叉树
- 满二叉树:每一层都满
- 完全二叉树:除最后一层都满,最后一层右边可以有缺失
- 深度=log2(n)+1
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
// 结构定义
typedef struct binarytreenode
{
int data;
struct binarytreenode *Lnode;
struct binarytreenode *Rnode;
} BiTNode,*BiTree;
// 创建二叉树
void creatTree(BiTree* Tree){
char c;
scanf("%c",&c);
if(c == '#'){
*Tree = NULL;
}else{
*Tree = (BiTNode*)malloc(sizeof(BiTNode));
(*Tree) -> data = c;
creatTree(&((*Tree) -> Lnode));
creatTree(&((*Tree) -> Rnode));
}
}
// 先序遍历
void pre(BiTree Tree){
if(!Tree){
return;
}
printf("%c",Tree -> data);
pre(Tree -> Lnode);
pre(Tree -> Rnode);
}
// 中序遍历
void mid(BiTree Tree){
if(!Tree){
return;
}
mid(Tree -> Lnode);
printf("%c",Tree -> data);
mid(Tree -> Rnode);
}
// 后序遍历
void after(BiTree Tree){
if(!Tree){
return;
}
after(Tree -> Lnode);
after(Tree -> Rnode);
printf("%c",Tree -> data);
}
线索二叉树
解决叶子节点的左右分支需要消耗空间。
-
若左子树为空,左指针指向前驱节点
-
若右子树为空,右指针指向后驱节点
-
ltag==0,指向左孩子;ltag==1,指向前驱结点
-
rtag==0,指向右孩子;rtag==1,指向后继结点
画图:先写出中序遍历,然后为左右节点为空的补上前驱或后继节点
先序线索二叉树的遍历:
TBTNode *First(TBTNode *p){
while(p->ltag == 0) p = p->lchild; //相当于找到树的最左下的结点
return p;
}
TBTNode *Next(TNTNode *p){
if(p->rtag == 0){
return First(p->rchild);
}
return p->rchild; //rtag=1,直接返回后继线索rchild,因为线索化后,rchild就是线索了,指向后继结点
}
void Inorder(TBTNode *root){
for(TBTNode *p = First(root); p != NULL; p = Next(p))
Visit(p);
}
- 画图
存储结构表示
双亲表示法
- 用数组表示树
在每个结点中,附设一个指示器parent指示其双亲结点到链表中的位置。
孩子表示法
- 用数组+链表表示树
把所有节点放在一个顺序数组中,为每一个节点的孩子们创建一个单链表,遍历该节点的所有孩子。
哈夫曼树
-
路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径
-
结点的路径长度:两结点之间路径上的分支数
-
树的路径长度:从树根到每一个结点的路径长度之和.记作:TL
-
权(weight):将树中结点赋给一个有着某种含义的数值,则这个数值为该结点的权
-
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积.
-
树的带权路径长度:树中所有叶子结点的带权路径长度之和.记作:WPL(Weighted Path Length)
上图中,WPL = (2+3) * 3 + 4 * 2 + 6 * 1 = 29 -
定义:最优二叉树,是一类带权路径长度最短的树
从权值数组构造:每一轮都将权值最小的作为左右结点归并成一个二叉树,然后把和值加入到序列中,循环直到只剩一个- 如:以w(1,4,24,5,6,10,8,31,22,11)构造哈夫曼树:
- 如:以w(1,4,24,5,6,10,8,31,22,11)构造哈夫曼树:
-
哈夫曼编码:每个节点左子树边为0,右边为1,一个节点的哈夫曼编码就是从根节点扫下来的读到的序列
图
- 连通:在无向图中,若从顶点v到顶点w有路径存在,则称v和w是连通的。
- 连通图:若图G中任意两个顶点都是连通的,则称图G为连通图
- 强连通:在有向图中,若从顶点v到顶点w和从顶点w到项点v之间都有路径,则称这两个顶点是强连通的。若图中任何一对顶点都是强连通的,则称此图为强连通图。
最小生成树
- 生成树定义:对一个具有 n 个点的连通图进行遍历,对于遍历后的子图,其包含原图中所有的点且保持图连通,最后的结构一定是一个具有 n-1 条边的树,通常称为生成树。
- 最小生成树: n-1 条边的边权之和是所有方案中最小
prim算法:
先随便往集合里加一个点,然后每次都找距离集合里任意一点距离最短的点加入,直到全部连接,见例子:
kruscal算法:
每迭代一次就选择一条不在当前树的最小代价边,加入到最小生成树的边集合里。
图的表示方法
邻接矩阵
无向图:
有向图:index指向columns
邻接表
先以数组存储,然后每个点的指针指向下一个点的下表
无向图:
有向图:
图的遍历
深度优先:
- 从当前节点出发,选择一个尚未访问过的相邻节点进行访问。
- 如果有多个未访问的相邻节点,随便选
- 如果当前节点的所有相邻节点都已经被访问过,或者没有未访问的相邻节点,算法会回溯到上一个节点,继续寻找其他未访问的相邻节点。
- 类比为走迷宫时尽可能沿着一个方向走到底,然后再回溯寻找未探索的路径。
- 类比为走迷宫时尽可能沿着一个方向走到底,然后再回溯寻找未探索的路径。
然后发现1没有邻接点,再选一个没有访问过的点,为唯一一个4,所以遍历序列为:
1 → 2 → 3 → 5 → 4
广度优先
以队列形式访问,把每一个节点的所有邻接节点入队,然后从队头出队访问,并把被访问的节点的邻接节点继续入队,以此类推
起点:节点 1
访问 1 → 标记为已访问。
将相邻节点 2, 3 加入队列。
队列:2, 3
访问节点 2(出队)
访问 2 → 标记为已访问。
将相邻未访问的节点 3, 5 加入队列。
队列:3, 3, 5
访问节点 3(出队)
访问 3 → 标记为已访问。
节点 3 的相邻节点是 3(自环,已访问),忽略。
队列:3, 5
访问节点 3(出队,再次访问,不重复标记)
忽略(已访问过)。
队列:5
访问节点 5(出队)
访问 5 → 标记为已访问。
将相邻未访问节点 3 加入队列(已访问,忽略)。
队列:空
检查未访问节点
未访问的节点还有 4,将其作为新起点。
队列:4
访问节点 4(出队)
访问 4 → 标记为已访问。
将相邻节点 1, 2, 5 加入队列(均已访问,忽略)。
队列:空
最终序列:1 → 2 → 3 → 5 → 4
最短路径
Dijkstra算法
类比prim算法,但是要把过程中得到的每一个路径值存下来
Floyd算法
构造矩阵,依次将节点作为中介加入矩阵,并重新计算受中介点影响的各个点之间的距离,将最短的距离更新到表中
加入A为中介后:
以此类推,得到:
AOV网 AOE网
- 在有向图中若以顶点表示活动,有向边表示活动之间的先后关系,这样的图简称为AOV网。
- 在带权有向图中若以顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间,这样的图简称为AOE网。
查找
哨兵顺序查找
- ASL:
- 成功:(n+1)/2
- 失败:n
- 遍历时只用进行1次判断
int sentrySearch(int[] arr, int target) {
if (target == arr[0]) {
return 0;
}
// 将第一个元素保存
int temp = arr[0];
// 将目标值放到0号下标出作为哨兵
arr[0] = target;
int index = arr.length - 1;
while (arr[index] != target) {
--index;
}
// 查找完毕,将0号下标元素还原
arr[0] = temp;
return index > 0 ? index : -1;
}
折半查找
- ASL:
int BSearch(int arr[],int length,int key){
int left = 0;
int right = length-1;
int mid;
while (left <= right)
{
mid = (left + right) / 2;
if(key == arr[mid]){
return mid;
}else if (key < arr[mid])
{
right = mid-1;
}else{
left = mid +1;
}
}
return -1;
}
分块查找
// 数据升序排序,分三块
#include <stdio.h>
struct index /*定义块的结构*/
{
int key;
int start;
int end;
} index_table[4]; /*定义结构体数组*/
int block_search(int key, int a[]) /*自定义实现分块查找*/
{
int i, j;
i = 1;
while (i <= 3 && key > index_table[i].key) /*确定在哪个块中*/
i++;
if (i > 3) /*大于分得的块数,则返回0*/
return 0;
j = index_table[i].start; /*j等于块范围的起始值*/
while (j <= index_table[i].end && a[j] != key) /*在确定的块内进行查找*/
j++;
if (j > index_table[i].end) /*如果大于块范围的结束值,则说明没有要查找的数,j置0*/
j = 0;
return j;
}
int main()
{
int i, j = 0, k, key, a[16];
printf("please input the number:\n");
for (i = 1; i < 16; i++)
scanf("%d", &a[i]); /*输入由小到大的15个数*/
for (i = 1; i <= 3; i++){
index_table[i].start = j + 1; /*确定每个块范围的起始值*/
j = j + 1;
index_table[i].end = j + 4; /*确定每个块范围的结束值*/
j = j + 4;
index_table[i].key = a[j]; /*确定每个块范围中元素的最大值*/
}
printf("please input the number which do you want to search:\n");
scanf("%d", &key); /*输入要查询的数值*/
k = block_search(key, a); /*调用函数进行查找*/
if (k != 0)
printf("success.the position is :%d\n", k); /*如果找到该数,则输出其位置*/
else
printf("not found!"); /*若未找到则输出提示信息*/
}
散列表查找
- 散列表:计算每个元素的hash来映射
当hash(a)=hash(b)的时候就要处理冲突
排序
二叉排序树
左 < 根 < 右
冒泡排序
- 时间复杂度:O( n^2 );
- 空间复杂度:最差O(n);平均O(1);最好n
void bubble_sort(int arr[], int len) {
int i, j, temp;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
选择排序
- 时间复杂度:O(N ^2)
- 空间复杂度:O(1)
void swap(int *a,int *b) //交換兩個變數
{
int temp = *a;
*a = *b;
*b = temp;
}
void selection_sort(int arr[], int len)
{
int i,j;
for (i = 0 ; i < len - 1 ; i++)
{
int min = i;
for (j = i + 1; j < len; j++) //走訪未排序的元素
if (arr[j] < arr[min]) //找到目前最小值
min = j; //紀錄最小值
swap(&arr[min], &arr[i]); //做交換
}
}
插入排序
- 时间复杂度:O(n^2);最好的情况:O(n)
- 空间复杂度:O(1)
void insertion_sort(int arr[], int len){
int i,j,key;
for (i=1;i<len;i++){
key = arr[i];
j=i-1;
while((j>=0) && (arr[j]>key)) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = key;
}
}
堆排序
- 升序用大根堆,降序用小根堆
- 时间复杂度:O(n log n),空间复杂度:O(1)
交换堆顶元素和末尾元素:将堆顶元素(最大值)与末尾元素交换,并将堆的大小减一。
调整堆:对堆顶元素进行调整,使其满足最大堆性质。
重复直到堆的大小为1
// 调整堆,使其满足最大堆性质
void heapify(vector<int>& arr, int n, int i) {
int largest = i; // 初始化最大值为根节点
int left = 2 * i + 1; // 左子节点
int right = 2 * i + 2; // 右子节点
// 如果左子节点大于根节点
if (left < n && arr[left] > arr[largest])
largest = left;
// 如果右子节点大于最大值
if (right < n && arr[right] > arr[largest])
largest = right;
// 如果最大值不是根节点
if (largest != i) {
swap(arr[i], arr[largest]); // 交换根节点和最大值节点
heapify(arr, n, largest); // 递归调整子树
}
}
// 构建最大堆
void buildMaxHeap(vector<int>& arr, int n) {
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
}
// 堆排序
void heapSort(vector<int>& arr) {
int n = arr.size();
// 构建最大堆
buildMaxHeap(arr, n);
// 一个个取出元素
for (int i = n - 1; i > 0; i--) {
swap(arr[0], arr[i]); // 将当前最大值移到数组末尾
heapify(arr, i, 0); // 调整堆
}
}
快速排序
key取arr[-1],left从左往右找找到比key大的,使得a[right]=a[left],然后right开始从右往左找,找到比key小的,使得a[left]=a[right],然后left又开始往右找,直到left=right完成一次循环。然后把a[left]=key,此时key左边都是比他小,右边都是比他大
void quickSort(int nums[], int start, int end) {
//数组有多个元素进行排序
if (start < end) {
int base = nums[start];//以要进行排序数组第0个元素为base
int left = start;//左指针
int right = end;//右指针
while (left < right) {
//从右向左找,比base大,right--
while (left< right && nums[right] >= base) {
right--;
}
//比base小,替换left所在位置的数字
nums[left] = nums[right];
//从左向右找,比base小,left++
while (left < right && nums[left] <= base){
left++;
}
//比base大,替换right所在位置的数字
nums[right] = nums[left];
}
nums[left] = base;//此时left=right,用base替换这个位置的数字
//排列比base小的数字的数组
quickSort(nums, start, left - 1);
//排列比base大的数字的数组
quickSort(nums, left + 1, end);
}
}
Comments NOTHING