郝斌老师数据结构学习笔记(评论区附视频和源代码)

本文主要是介绍郝斌老师数据结构学习笔记(评论区附视频和源代码),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 数据结构概述
    • 定义
    • 数据结构
      • 逻辑结构
        • 线性结构
        • 非线性结构
      • 物理结构
      • 算法
      • 数据结构的地位
  • 预备知识
    • 指针
      • 指针的重要性
      • 定义
        • 地址
        • 指针
      • 分类
        • 基本类型指针
        • 指针和数组的关系
          • 指针和一维数组
            • 数组名
            • 下标的指针的关系
        • 结构体
          • 为什么需要结构体
          • 什么叫结构体
          • 如何使用结构体
          • 注意事项
        • 动态内存的分配和释放
  • 模块一:线性结构【把所有的结点用一根直线穿起来】
    • 连续存储【数组】
    • 离散存储【链表】
      • 定义
        • 专业术语
        • 如果希望通过一个函数来对链表进行处理,我们至少需要接收链表的哪些参数
      • 分类
        • 单链表
        • 双链表
        • 循环链表
        • 非循环链表
      • 算法
        • 遍历
        • 查找
        • 清空
        • 销毁
        • 求长度
        • 排序
        • 删除节点
        • 插入节点
      • 链表的优缺点
    • 线性结构的两种常见应用之一 栈
      • 定义
      • 应用
    • 线性结构的两种常见应用之二 队列
      • 定义
      • 分类
        • 静态队列
      • 队列算法
      • 队列的具体应用
    • 专题:递归
      • 定义
      • 递归满足三个条件
      • 循环和递归
        • 递归:
        • 循环
      • 递归案例
      • 递归的应用
  • 模块二:非线性结构
      • 定义
        • 专业术语
      • 分类
        • 一般树
        • 二叉树(有序树)
          • 分类
        • 森林
      • 树的存储【重点】
        • 二叉树的存储【重点】
          • 连续存储 [完全二叉树]【重点】
          • 链式存储![image-20221113154137513](https://img-blog.csdnimg.cn/img_convert/7b8d6744d45c6c35039b2d329ddb2a26.png)
        • 一般树的存储
        • 森林的存储
      • 二叉树操作
        • 先序遍历[先访问根节点]
        • 中序遍历[中间访问根节点]
        • 后续遍历[最后访问根节点]
      • 已知两种遍历序列求原始二叉树
  • 模块三:查找和排序
    • 折半查找
    • 排序:
      • 冒泡
      • 插入
      • 选择
      • 快速排序
      • 归并排序
  • Java中容器和数据结构相关知识
    • Iterator接口
    • Map
      • 哈希表


数据结构概述

定义

我们如何把现实中大量而复杂的问题以特定的数据类型(个体)和特定的存储结构(个体关系)保存到主存储器(内存)中,以及在此基础上为实现某个功能(比如查找某个元素,删除某个元素,对所有元素进行排序)而执行的相应操作,这个操作也叫算法。

数据结构 = 个体 + 个体关系
算法 = 对存储数据的操作

数据结构

  1. 线性结构

特点:除第一个元素只有一个“后继”和最后一个元素只有一个“前驱”,其它每个元素只有一个“前驱”元素和一个“后继”元素。

常见的线性结构有: 数组、链表、栈以及队列。****注意:栈和队列本身不是一种数据结构,可通过数组或链表实现。

1.1 线性结构又分为顺序存储和链式存储

线性结构又分为顺序存储和链式存储,顺序存储:****各个元素存储的地址空间连续,逻辑相邻的元素在物理内存中也相邻,如数组;

链式存储*****:各个元素存储在任意的地址空间,逻辑相邻的元素在物理内存中没有联系,如链表。*

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-146nnUNX-1668509707370)(https://cyuyan.oss-cn-beijing.aliyuncs.com/img/wps1.jpg)]

1.2 顺序存储和链式存储的区别

1.2.1 顺序存储

优点:① 因为各个元素是连续储存*,而且当元素类型一致时占用空间大小一致*,所以可以直接通过首元素地址计算某个元素的内存地址,从而*****访问特定元素*效率很高

② 对于有序数组*,还可以通过二分查找提高元素查找*的速度

缺点:① 由于顺序存储的特点,所以在删除或插入元素后需要移动其它元素使得整体的存储空间依然是连续的,所以*****删除、插入元素效率低,如下图*。

② 由于元素存储空间连续,所以当有大数据时,****较难分配一块连续的大内存空间

img

1.2.2 链式存储

优点:① 由于链式存储的特点,删除或插入节点很方便,不需要移动其它元素,改变元素“连接”关系即可,所以删除、插入元素效率高,如下图。

缺点:① 因为存储的任意性,只能通过前一个元素访问下一个元素,每一次访问元素都从头节点开始遍历,所以*****访问特定元素或查找元素效率低*。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sfYGvjQJ-1668509707371)(https://cyuyan.oss-cn-beijing.aliyuncs.com/img/wps3.jpg)]

非线性结构

特点:每个元素可以和多个元素“连接”。

常见的非线性结构:二维数组、树和图。

逻辑结构

线性结构

数组

链表

栈和队列是一种特殊的线性结构

非线性结构

物理结构

算法

解题的方法和步骤

衡量算法的标准

  1. 时间复杂度
    大概程序要执行的次数,而非执行的时间
  2. 空间复杂度
    算法执行过程中大概所占用的最大内存
  3. 难易程度
  4. 健壮性

数据结构的地位

数据结构是软件中最核心的课程

程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言

预备知识

指针

指针的重要性

指针是c语言的灵魂

定义

地址

内存单元的编号

从0开始的非负整数

范围:0 – FFFFFFFF【0 – 4G-1】

指针

指针就是地址 地址就是指针

指针变量是存放内存单元地址的变量

指针的本质是一个操作受限的非负整数【只能在某种情况下相减】

分类

基本类型指针
# include <stdio.h>int main(void)
{int * p;//p是一个变量名,int *表示该p变量只能存储int类型变量的地址int i = 10;int j;p = &i;//p = 10;//errorj = *p;printf("i = %d,j = %d,*p = %d\n",i,j,*p);
}
指针和数组的关系
指针和一维数组
数组名

一维数组名是个指针常量

它存放的是一维数组第一个元素的地址

它的值不能改变

一维数组名指向的是数组的第一个元素

下标的指针的关系

a[i] <<==>> *(a+1)

/*数组名a是一个指针常量a保存的是第一元素的地址*a就是第一个元素故 *(a+i)就是第i+1个元素
*/# include <stdio.h>int main(void)
{int a[5] = {1,2,3,4,5};a[3] == *(a+3);3[a] == *(3+a);//把方括号理解为一种运算符 
}

假设指针变量的名字是p

则p+i的值是第i+1个元素的值

# include <stdio.h>int main(void)
{int a[5] = {1,2,3,4,5};printf("%p\n",a+1);printf("%p\n",a+2);printf("%p\n",a+3);//*a+3等价于a[0]+3return 0;
}/*
000000000062FE04
000000000062FE08
000000000062FE0C
*/

p + i的值是 p + i * (p所指向的变量所占的字节数)

结构体
为什么需要结构体

为了表示一些复杂的数据,而普通的基本变量无法满足要求。

什么叫结构体

结构体是用户根据实际需要自己定义的复合数据类型

如何使用结构体
两种方式
struct Student st = {1000,"zhangsan",20};
struct Student * pst = &st;
1.st,sid;
2.pst->sid;pst 所指向的结构体变量中的sid这个变量
注意事项

结构体变量不能加减乘除,但可以相互赋值

普通结构体变量和结构体指针变量作为函数传参的问题

​ 传给结构体变量的首地址即可

#include<stdio.h>
#include<string.h>void f(struct Student * pst);
void g(struct Student st);//占据内存 
void g2(struct Student * st1);struct Student		//定义了一个数据类型 
{int sid;char name[100];int age;
};int main(void)
{struct Student st;f(&st);g(st);
//	printf("%d %s %d\n",st.age,st.name,st.sid);g2(&st);return 0;
}void f(struct Student * pst)
{pst->sid = 4000;strcpy(pst->name,"zhang");pst->age = 23;
}void g(struct Student st)
{printf("%d %s %d\n",st.age,st.name,st.sid);
}void g2(struct Student * st1)
{printf("%d %s %d\n",st1->age,st1->name,st1->sid);
}
动态内存的分配和释放
# include <stdio.h>
# include <malloc.h>int main(void)
{int a[5] = {1,2,3,4,5};int len,i;scanf("%d",&len);int * pArr = (int *)malloc(sizeof(int)*len);//动态分配完内存单元后,强制转换为int *类型,也就是将内存区块化,4个4个分好//	pArr[1] = 10;
//	printf("%d\n",*(pArr +1)); *pArr = 10;			//类似于a[0] = 10; *(pArr+3) = 23;		//如果p是个指针变量,则 p[i] 永远等价于 *(p+i), 类似于a[3] = 23;pArr[2] = 21;		//类似于a[2] = 21;printf("%d %d %d\n",pArr[0],pArr[3],pArr[2]); //我们可以吧pArr当做普通数组来使用for(i = 0;i<len;i++){scanf("%d",&pArr[i]);}for(i = 0;i<len;i++){printf("%d\n",*(pArr+i));}free(pArr);			//把pArr所代表的动态分配的内存释放 return 0;
}
# include <stdio.h>
# include <malloc.h>struct Student 
{int sid;int age;
};struct Student *CreateStudent(void);
void ShowStudent(struct Student * pst);int main()
{struct Student * st;st = CreateStudent();ShowStudent(st);return 0;
} struct Student *CreateStudent(void)
{struct Student * p = (struct Student *)malloc(sizeof(struct Student));p->sid = 88;p->age = 66;return p;
}void ShowStudent(struct Student * pst)
{printf("%d %d\n",pst->sid,pst->age);
}

模块一:线性结构【把所有的结点用一根直线穿起来】

连续存储【数组】

什么叫数组

元素类型相同,大小相等

数组的优缺点

image-20221001173136793

离散存储【链表】

定义

  1. n个节点离散分配
  2. 彼此通过指针相连
  3. 每个节点只有一个前驱节点,每个节点只有一个后继节点
  4. 首节点没有前驱结点,尾结点没有后继节点
专业术语

首节点:第一个有效节点

尾节点:最后一个有效节点

头结点:第一个有效节点之前的那个节点,头结点并不存放有效数据,加头结点的目的主要是为了方便对链表的操作

头指针:指向头结点的指针变量

尾指针:指向尾结点的指针变量

image-20221002091447336
如果希望通过一个函数来对链表进行处理,我们至少需要接收链表的哪些参数

只需要一个参数:头指针

因为我们通过头指针可以推算出链表的其他所有参数

image-20221002082930861

分类

泛型:利用某种技术达到的效果就是:不同的存储方式,执行的操作是一样的。

单链表
双链表

每一个节点有两个指针域

循环链表

能通过任何一个节点找到其它所有的节点

非循环链表

算法

遍历
查找
清空
销毁
求长度
排序
删除节点

伪算法分析

image-20221002101936913

插入节点

伪算法分析image-20221002101346681image-20221002153510958

# include <stdio.h>
# include <malloc.h>
# include <stdlib.h>typedef struct Node
{int data; //数据域struct Node * pNext; //指针域
}NODE, *PNODE; //NODE等价于struct Node    PNODE等价于struct Node *//函数声明
PNODE create_list(void);  //创建链表
void traverse_list(PNODE pHead);  //遍历链表
bool is_empty(PNODE pHead);  //判断链表是否为空
int length_list(PNODE);  //求链表长度
bool insert_list(PNODE pHead, int pos, int val);  //在pHead所指向链表的第pos个节点的前面插入一个新的结点,该节点的值是val, 并且pos的值是从1开始
bool delete_list(PNODE pHead, int pos, int * pVal);  //删除链表第pos个节点,并将删除的结点的值存入pVal所指向的变量中,  并且pos的值是从1开始
void sort_list(PNODE);  //对链表进行排序int main(void)
{PNODE pHead = NULL; //等价于 struct Node * pHead = NULL;int val;pHead = create_list();  //create_list()功能:创建一个非循环单链表,并将该链表的头结点的地址付给pHeadtraverse_list(pHead);//insert_list(pHead, -4, 33);if ( delete_list(pHead, 4, &val) ){printf("删除成功,您删除的元素是: %d\n", val);}else{printf("删除失败!您删除的元素不存在!\n");}traverse_list(pHead);//int len = length_list(pHead);//printf("链表的长度是%d\n", len);//sort_list(pHead);//traverse_list(pHead);/*	if ( is_empty(pHead) )printf("链表为空!\n");elseprintf("链表不空!\n");
*/return 0;
}PNODE create_list(void)
{int len;  //用来存放有效节点的个数int i;int val; //用来临时存放用户输入的结点的值//分配了一个不存放有效数据的头结点PNODE pHead = (PNODE)malloc(sizeof(NODE));if (NULL == pHead){printf("分配失败, 程序终止!\n");exit(-1);}PNODE pTail = pHead;pTail->pNext = NULL;printf("请输入您需要生成的链表节点的个数: len = ");scanf("%d", &len);for (i=0; i<len; ++i){printf("请输入第%d个节点的值: ", i+1);scanf("%d", &val);PNODE pNew = (PNODE)malloc(sizeof(NODE));if (NULL == pNew){printf("分配失败, 程序终止!\n");exit(-1);}pNew->data = val;pTail->pNext = pNew;pNew->pNext = NULL;pTail = pNew;}return pHead;
}void traverse_list(PNODE pHead)
{PNODE p = pHead->pNext;while (NULL != p){printf("%d  ", p->data);p = p->pNext;}printf("\n");return;
}bool is_empty(PNODE pHead)
{if (NULL == pHead->pNext)return true;elsereturn false;
}int length_list(PNODE pHead)
{PNODE p = pHead->pNext;int len = 0;while (NULL != p){++len;p = p->pNext;}return len;
}void sort_list(PNODE pHead)
{int i, j, t;int len = length_list(pHead);PNODE p, q;for (i=0,p=pHead->pNext; i<len-1; ++i,p=p->pNext){for (j=i+1,q=p->pNext; j<len; ++j,q=q->pNext){if (p->data > q->data)  //类似于数组中的:  a[i] > a[j]{t = p->data;//类似于数组中的:  t = a[i];p->data = q->data; //类似于数组中的:  a[i] = a[j];q->data = t; //类似于数组中的:  a[j] = t;}}}return;
}//在pHead所指向链表的第pos个节点的前面插入一个新的结点,该节点的值是val, 并且pos的值是从1开始
bool insert_list(PNODE pHead, int pos, int val)
{int i = 0;PNODE p = pHead;while (NULL!=p && i<pos-1){p = p->pNext;++i;}if (i>pos-1 || NULL==p)return false;//如果程序能执行到这一行说明p已经指向了第pos-1个结点,但第pos-1个节点是否存在无所谓//分配新的结点PNODE pNew = (PNODE)malloc(sizeof(NODE));if (NULL == pNew){printf("动态分配内存失败!\n");exit(-1);}pNew->data = val;//将新的结点存入p节点的后面PNODE q = p->pNext;p->pNext = pNew;pNew->pNext = q;return true;
}bool delete_list(PNODE pHead, int pos, int * pVal)
{int i = 0;PNODE p = pHead;while (NULL!=p->pNext && i<pos-1){p = p->pNext;++i;}if (i>pos-1 || NULL==p->pNext)return false;//如果程序能执行到这一行说明p已经指向了第pos-1个结点,并且第pos个节点是存在的PNODE q = p->pNext;  //q指向待删除的结点*pVal = q->data;  //删除p节点后面的结点p->pNext = p->pNext->pNext;//释放q所指向的节点所占的内存free(q);q = NULL;return true;}

链表的优缺点

线性结构的两种常见应用之一 栈

动态分配的内存在堆中,静态分配的内存在栈中

定义

定义:一种可以实现“先进后出”的存储结构。栈类似于纸箱

分类:静态栈(以数组为底层)

​ 动态栈(以链表为底层)

算法:出栈

压栈(进栈)

image-20221004202118515

算法:狭义的算法是与数据的存储方式密切相关的

​ 广义的算法是与数据的存储方式无关的

泛型:利用某种技术达到的效果是:不同的存储方式,执行的操作是一样的。

# include<stdio.h>
# include<malloc.h>
# include<stdlib.h>
# include<stdbool.h>typedef struct Node
{int data;struct Node * pNext;
}NODE,* PNODE;typedef struct Stack
{PNODE pTop;PNODE pBottom;
}STACK,* PSTACK;void init(PSTACK);//初始化栈
void push(PSTACK pS, int val);
void pushs(PSTACK);
void traverse(PSTACK);
bool pop(PSTACK, int *);
void clear(PSTACK pS);
bool empty(PSTACK pS);int main(int argc, char const *argv[])
{STACK S;int val;init(&S);pushs(&S);traverse(&S);if(pop(&S,&val)){printf("出栈成功,出栈的元素为:%d\n\n",val);}else{printf("栈为空!");}traverse(&S);clear(&S);traverse(&S);return 0;
}void init(PSTACK pS)
{pS->pTop = (PNODE)malloc(sizeof(NODE));if (NULL == pS->pTop){printf("分配失败,退出程序");exit(-1);}else{pS->pBottom = pS->pTop;pS->pTop->pNext = NULL;}printf("栈初始化成功!\n\n");
}//多次压栈
void pushs(PSTACK pS)
{int x,y,val;printf("请输入您要压栈的次数:");scanf("%d",&x);for (int i = 0; i < x; i++){printf("第%d次压栈的数值为:",i+1);scanf("%d",&val);PNODE pNew = (PNODE)malloc(sizeof(NODE));pNew->data = val;pNew->pNext = pS->pTop;pS->pTop = pNew;}printf("已经全部压栈成功\n\n");return;
}void push(PSTACK pS, int val)
{PNODE pNew = (PNODE)malloc(sizeof(NODE));pNew->data = val;pNew->pNext = pS->pTop;pS->pTop = pNew;return;
}//输出与压栈的顺序相反,刚好表明先进后出
void traverse(PSTACK pS)
{if (empty(pS)){printf("栈为空");}else{PNODE p = pS->pTop;printf("栈中元素为:");while (p != pS->pBottom){printf("%d	",p->data);p = p->pNext;}printf("\n\n");}return;
}bool empty(PSTACK pS)
{if(pS->pBottom == pS->pTop)return true;elsereturn false;
}bool pop(PSTACK pS, int * pVal)
{if(empty(pS))return false;else{PNODE r = pS->pTop;*pVal = r->data;pS->pTop = r->pNext;free(r);r = NULL;return true;		}
}//清空栈
void clear(PSTACK pS)
{if(empty(pS)){return;}else{PNODE p = pS->pTop;PNODE q = NULL;while (p != pS->pBottom){q = p->pNext;free(p);p = q;}pS->pTop = pS->pBottom;}printf("已清空栈");
}

应用

函数调用

中断

表达式求值

内存分配

缓冲处理

迷宫

线性结构的两种常见应用之二 队列

定义

一种可以实现“先进先出”的存储结构

队头(删):front,队尾(增):rear

分类

链式队列 —— 用链表实现

静态队列 —— 用数组实现

静态队列

静态队列通常都必须是循环队列

循环队列的讲解

  1. 静态队列为什么必须是循环队列
    队头和队尾都只能加,浪费内存,不合理

  2. 循环队列需要几个参数来确定
    两个参数:front,rear

  3. 循环队列各个参数的含义

    两个参数不同场合有不同的含义
    建议初学者先记住,慢慢体会
    1、队列初始化
    front和rear的值都是0

    ​ 2、队列非空

    ​ front代表的是队列的第一个元素

    ​ rear代表的是队列的最后一个有效元素的下一个元素

    ​ 3、队列空
    ​ front和rear的值相等,但不一定是零

  4. 循环队列入队伪算法讲解
    Snipaste_2022-11-08_20-29-10

  5. 循环队列出队伪算法讲解
    front= (front+1)%数组的长度

  6. 如何判断循环队列是否为空
    front = rear

  7. 如何判断循环队列是否已满
    预备知识:
    front的值可能比rant大,也可能比rear小,当然也可能两者相等
    两种方式:
    1、多增加一个标识参数
    2、少用一个元素【通常用第二种】
    Snipaste_2022-11-08_21-03-37

队列算法

入队

出队

# include <stdio.h>
# include <malloc.h>
# include <stdbool.h>typedef struct Queue
{int * pBase;int front;int rear;
}QUEUE;void init(QUEUE *);
bool en_queue(QUEUE *,int);
bool full_queue(QUEUE * pQ);
bool empty(QUEUE * pQ);
void traverse_queue(QUEUE * pQ);
bool out_queue(QUEUE * pQ,int * pVal);int main(int argc, char const *argv[])
{QUEUE Q;int val;init(&Q);en_queue(&Q,1);en_queue(&Q,2);en_queue(&Q,3);en_queue(&Q,4);en_queue(&Q,5);en_queue(&Q,6);en_queue(&Q,7);traverse_queue(&Q);if (out_queue(&Q,&val)){printf("出队成功,被删除的元素为:%d\n",val);}elseprintf("出队失败");traverse_queue(&Q);return 0;
}void init(QUEUE * pQ)
{pQ->pBase = (int *)malloc(sizeof(int)*6);pQ->front = 0;pQ->rear = 0;
}bool full_queue(QUEUE * pQ)
{if ((pQ->rear+1)%6 == pQ->front){return true;}elsereturn false;}bool en_queue(QUEUE *pQ,int val)
{if (full_queue(pQ)){printf("队已满,无法添加\n");return false;}else{pQ->pBase[pQ->rear] = val;pQ->rear = (pQ->rear + 1) % 6;printf("添加成功!\n");return true;}
}void traverse_queue(QUEUE * pQ)
{int i = pQ->front;printf("队内元素为:");while ((i != pQ->rear)){printf("%d  ",pQ->pBase[i]);i = (i+1)%6;}printf("\n");return;    
}bool empty(QUEUE * pQ)
{if (pQ->front == pQ->rear){return true;}else {   return false;}}bool out_queue(QUEUE * pQ,int * pVal)
{if (empty(pQ)){printf("队列为空!\n");return false;}else{*pVal = pQ->pBase[pQ->front];pQ->front = (pQ->front+1)%6;  return true;}
}

队列的具体应用

所有和时间有关的操作都有队列的影子

专题:递归

定义

一个函数自己直接或者间接调用自己

image-20221112170158231

递归满足三个条件

  1. 递归必须得有一个明确的终止条件
  2. 该函数所处理的数据规模必须在递减
  3. 这个转化必须是可解的

循环和递归

递归:

易于理解

数度慢

存储空间大

循环

不易理解

速度快

存储空间小

递归案例

1.1+2+3+4+…+100

# include <stdio.h>long sum(int n)
{if(1 == n)return 1;elsereturn n + sum(n - 1);
}int main(int argc, char const *argv[])
{printf("%ld\n",sum(100));return 0;
}

2.求阶乘

# include <stdio.h>//假定n的值是1或大于1的值
long f(long n)
{if (1 == n)return 1;elsereturn f(n-1) * n;
}int main(void)
{printf("%ld\n", f(20));return 0;
}

3.汉诺塔

# include <stdio.h>void hannuota(int n, char A, char B, char C)
{
/*如果是1个盘子直接将A柱子上的盘子从A移到C否则先将A柱子上的n-1个盘子借助C移到B直接将A柱子上的第n个盘子从A移到C最后将B柱子上的n-1个盘子借助A移到C
*/if (1 == n){printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);}else{hannuota(n-1, A, C, B);printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);hannuota(n-1, B, A, C);}
}int main(void)
{char ch1 = 'A';char ch2 = 'B';char ch3 = 'C';int n;printf("请输入要移动盘子的个数: ");scanf("%d", &n);hannuota(n, 'A', 'B', 'C');return 0;
}

4.走迷宫

递归的应用

树和森林就是以递归的方式定义的

树和图的很多算法都是以递归来实现的

很多数学公式就是以递归的方式定义的

​ 斐波拉契序列

​ 1 2 3 5 8 13 21 34 55 89 144 233

模块二:非线性结构

定义

专业定义:

  1. 有且只有一个称为跟的节点
  2. 有若干个互不相交的子树,这些子树本身也是一棵树

通俗定义

  1. 树是由节点和边组成
  2. 每个节点只有一个父节点但可以有多个子节点
  3. 但有一个节点例外,该节点没有父节点,此节点称为跟节点
专业术语

节点:圈

父节点

子节点

子孙

堂兄弟

深度:从根节点到最底层节点的层数称之为深度,根节点是第一层。

叶子节点:没有子节点的节点

非终端节点:实际上就是非叶子节点

度:子节点的个数称为度(看该节点有几个孩子)

​ 树的度:一个树中最大的度

分类

一般树

任意一个节点的子节点的个数都不受限制

二叉树(有序树)

任意一个节点的子节点的个数最多两个。且子节点的位置不可更改(左边的叫左子树,右边的叫右子树)

分类

一般二叉树

满二叉树:在不增加树的层数的前提下,无法再多添加一个节点的二叉树就是满二叉树

完全二叉树:如果只是删除了满二叉树做底层最右边的连续若干个节点,这样形成的二叉树叫完全二叉树。

森林

n个互不相交的树的集合

树的存储【重点】

二叉树的存储【重点】
连续存储 [完全二叉树]【重点】

优点:查找某个节点的父节点和子节点(也包括判断有没有子节点)数组很快

缺点:耗用内存过大

链式存储image-20221113154137513
一般树的存储

双亲表示法image-20221113154700843

孩子表示法

image-20221113154853352

双亲孩子表示法image-20221113155252090

二叉树表示法【也叫孩子兄弟链表表示法】

先把一般树转化为二叉树,在存储二叉树

一般树转化为二叉树的方法是:

​ 设法保证任意一个节点的

​ 左指针域指向他的第一个孩子

​ 右指针域指向他的下一个兄弟

​ 只要满足此条件,就可以把一个普通树转化为二叉树来存储

image-20221113160104677

森林的存储

先把森林转化为二叉树,再存储二叉树

image-20221113161315078

二叉树操作

先序遍历[先访问根节点]

先访问根节点

再先序访问左子树

再先序访问右子树

image-20221113195822507

中:DBAECGF

后:DBEGFCA

image-20221113200009893

先: ABCDEFLQMNS

中:CDFELBAMSNQ

后:FLEDCBSNMQA

image-20221113200449662

先:ABQLCDGEF

中:QBLAGDCEF

后:QLBGDFECA

中序遍历[中间访问根节点]

中序遍历左子树

再访问根节点

再中序遍历右子树

image-20221113201400485

先:ABCDEFLMNQ

中:BDCEALFNQM

后:DECBLQNMFA

image-20221113202316806

先:ABCDEMQLN

中:BDCAMQELN

后:DCBQMNLEA

后续遍历[最后访问根节点]

先中序遍历左子树

再中序遍历右子树

再访问根节点

image-20221113203054980

先:ABCDEFML

中:BADCMFEL

image-20221113203802963

先:MNQSTWLPF

中:NMWTSQLPF

后:NWTSFPLQM

# include <stdio.h>
# include <malloc.h>typedef struct BTNode
{char data;struct BTNode * pLchild;struct BTNode * pRchild;
}BTNODE,* PBTNODE;void PostTraverseBTree(PBTNODE pT);
void InTraverseBTree(PBTNODE pT);
void PreTraverseBTree(PBTNODE pT);
PBTNODE CreateBTree();int main(int argc, char const *argv[])
{PBTNODE pT = CreateBTree();// PreTraverseBTree(pT);// InTraverseBTree(pT);PostTraverseBTree(pT);return 0;
}PBTNODE CreateBTree()
{PBTNODE pA = (PBTNODE)malloc(sizeof(BTNODE));PBTNODE pB = (PBTNODE)malloc(sizeof(BTNODE));PBTNODE pC = (PBTNODE)malloc(sizeof(BTNODE));PBTNODE pD = (PBTNODE)malloc(sizeof(BTNODE));PBTNODE pE = (PBTNODE)malloc(sizeof(BTNODE));pA->data = 'A';pB->data = 'B';pC->data = 'C';pD->data = 'D';pE->data = 'E';pA->pLchild = pB;pA->pRchild = pC;pB->pLchild = pB->pRchild = NULL;pC->pLchild = pD;pC->pRchild = NULL;pD->pLchild = pE;pD->pRchild = NULL;pE->pLchild = pE->pRchild = NULL;return pA;
}void PreTraverseBTree(struct BTNode * pT)
{if (NULL != pT){printf("%c\n", pT->data);if (NULL != pT->pLchild){PreTraverseBTree(pT->pLchild);}if (NULL != pT->pRchild){PreTraverseBTree(pT->pRchild);//pT->pLchild可以代表整个左子树}	}	/*伪算法先访问根节点再先序访问左子树再先序访问右子树
*/
}void InTraverseBTree(PBTNODE pT)
{if(NULL != pT){if (NULL != pT->pLchild){InTraverseBTree(pT->pLchild);}printf("%c\n",pT->data);if (NULL != pT->pRchild){InTraverseBTree(pT->pRchild);}}
}void PostTraverseBTree(PBTNODE pT)
{if(NULL != pT){if (NULL != pT->pLchild){PostTraverseBTree(pT->pLchild);}if (NULL != pT->pRchild){PostTraverseBTree(pT->pRchild);}printf("%c\n",pT->data);}
}

已知两种遍历序列求原始二叉树

通过先序和中序 或者 中序和后续 我们可以还原出原始的二叉树

但是通过先序和后续是无法还原出原始的二叉树的

换种说法:只有通过先序和中序,或者中序和后续 ,我们才可以唯一的确定一个二叉树

思路:不断的去根据先序和后续确定每一个根节点,然后根据中序判断左右节点的分布。

先 中:

image-20221113220232753

image-20221113221301341

中 后:

image-20221113222334639

image-20221114151150612

先:A B C D E F G L H

中:C D B A F L G E H

后:D C B L G F H E A

image-20221114152944328

模块三:查找和排序

折半查找

排序:

冒泡

插入

选择

快速排序

# include <stdio.h>int FindPos(int * a,int low,int high);
void QuickSort(int * a,int low,int high);int main(int argc, char const *argv[])
{int a[6] = {4,7,-2,8,1,5};int i;QuickSort(a,0,5);for(i = 0;i<6;i++){printf("%d  ",a[i]);}printf("\n");return 0;
}void QuickSort(int * a,int low,int high)
{int pos;if(low < high){pos = FindPos(a,low,high);QuickSort(a,low,pos-1);QuickSort(a,pos+1,high);}
}int FindPos(int * a,int low,int high)
{int val = a[low];while (low < high){while(low < high && a[high] >= val){--high;}a[low] = a[high];while(low < high && a[high] <= val){++low;}a[high] = a[low];}a[low] = val; return low;
}

归并排序

Java中容器和数据结构相关知识

Iterator接口

Map

哈希表

这篇关于郝斌老师数据结构学习笔记(评论区附视频和源代码)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/312708

相关文章

Python视频处理库VidGear使用小结

《Python视频处理库VidGear使用小结》VidGear是一个高性能的Python视频处理库,本文主要介绍了Python视频处理库VidGear使用小结,文中通过示例代码介绍的非常详细,对大家的... 目录一、VidGear的安装二、VidGear的主要功能三、VidGear的使用示例四、VidGea

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss