智能指针 ????
数据结构
逻辑结构
线性:数组 链表
栈和队列是一种特殊的线性结构
非线性:树 图
物理结构
02 链表
定义
n个结点离散分配
彼此通过指针通过指针想连
每一个结点只有一个前驱结点,每一个结点只有一个后继结点
头结点没有前驱结点,尾结点没有后继节点
专业术语
首结点:第一个有效结点
尾结点:最后一个有效结点
头结点:第一个有效结点之前的那个节点
头结点并不存放有效数据
加头结点的目的是方便链表的操作
头指针:指向头结点的指针变量
尾指针:指向尾结点的指针变量
确定一个链表需要几个参数:
只要一个参数:头指针
通过头指针可以推算出链表的所有参数
分类
单链表
双链表:每一个结点有两个指针域
循环链表:能通过一个结点能找到其他所有的结点
非循环链表
算法
遍历
查找
清空
销毁
求长度
排序
删除结点
//先临时定义一个指向p后面结点的指针r
r = p->pNext;//r指向p后面的那个结点
p->pNext = p->pNext->pNext;
delete r;
插入结点
//第一种
//先临时定义一个指向p后面结点的指针r
r = p->pNext;//r指向p后面的那个结点
p->pNext = q;
q->pNext = r;
//第二种
q->pNext = p->pNext;
p->pNext = q;
单链表的创建以及遍历
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
typedef struct Node
{
int data;
struct Node * pNext;
}NODE,*PNODE;
//创建一个单链表
PNODE createList()
{
int i;
int len;
int val;
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if (pHead == NULL)
{
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 (pNew == NULL)
{
printf("分配内存失败,程序终止\n");
exit(-1);
}
pNew->data = val;
pTail->pNext = pNew;
pNew->pNext = NULL;
pTail = pNew;
}
return pHead;
}
void traverseList(PNODE pHead)
{
PNODE p = pHead->pNext;
while (p != NULL)
{
printf("%d ",p->data);
p = p->pNext;
}
printf("\n");
return;
}
//判断链表是否为空
bool isEmpty(PNODE pHead)
{
if (pHead->pNext == NULL)
return true;
else
return false;
}
//求链表的长度
int lengthList(PNODE pHead)
{
PNODE p = pHead->pNext;
int len = 0;
while (p!=NULL)
{
++len;
p = p->pNext;
}
return len;
}
//在pos所指向链表第pos个结点前面插入一个新的结点,该结点值为val
bool insertList(PNODE pHead, int pos, int val)
{
int i = 0;
PNODE p = pHead;
while (p != NULL && i < pos - 1)
{
p = p->pNext;
i++;
}
if (p == NULL || i > pos - 1)
return false;
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (pNew == NULL)
{
printf("分配失败,程序终止\n");
exit(-1);
}
pNew->data = val;
PNODE q = p->pNext;
p->pNext = pNew;
pNew->pNext = q;
return true;
}
//删除结点
bool deleteList(PNODE pHead, int pos, int * val)
{
int i = 0;
PNODE p = pHead;
while (p->pNext != NULL && i < pos - 1)
{
p = p->pNext;
i++;
}
if (p->pNext == NULL || i > pos - 1)
return false;
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (pNew == NULL)
{
printf("分配失败,终止程序\n");
exit(-1);
}
PNODE q = p->pNext;
*val = q->data;
//删除p结点后面的结点
p->pNext = p->pNext->pNext;
free(q);
q = NULL;
return true;
}
int main()
{
PNODE pHead = NULL;
int val;
pHead = createList();
traverseList(pHead);
//if (isEmpty(pHead))
// printf("链表为空!\n");
//else
// printf("链表不为空\n");
//int len = lengthList(pHead);
//printf("该链表的长度为:%d\n",len);
//insertList(pHead,4,33);
//traverseList(pHead);
if (deleteList(pHead, 4, &val))
{
printf("删除成功,您删除的元素是:%d\n",val);
}
else
{
printf("删除失败!\n");
}
traverseList(pHead);
system("pause");
return 0;
}
03 栈
定义
一种可以实现“先进后出”的存储结构
静态内存在栈内分配(压栈,出栈)
动态内存在堆上分配(堆排序)
分类
静态栈(类似于数组)
动态栈(类似于链表)
算法
出栈
压栈
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PLBp4RGb-1606466546214)(C:\Users\52675\AppData\Roaming\Typora\typora-user-images\image-20201125100547022.png)]
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
typedef struct Node
{
int data;
struct Node * pNext;
}NODE, *PNODE;
typedef struct stack
{
PNODE pTop;//顶部
PNODE pBottom;//底部
}STACK, *PSTACK;
void initStack(PSTACK pS)
{
pS->pTop = (PNODE)malloc(sizeof(NODE));
if (pS->pTop == NULL)
{
printf("动态内存分配失败\n");
exit(-1);
}
else
{
pS->pBottom = pS->pTop;
pS->pTop->pNext = NULL;
}
return;
}
void pushStack(PSTACK pS, int val)
{
PNODE pNew = (PNODE)malloc(sizeof(NODE));
pNew->data = val;
pNew->pNext = pS->pTop;
pS->pTop = pNew;
return;
}
void traverseStack(PSTACK pS)
{
PNODE p = pS->pTop;
while (p != pS->pBottom)
{
printf("%d ", p->data);
p = p->pNext;
}
printf("\n");
return;
}
bool empty(PSTACK pS)
{
if (pS->pTop == pS->pBottom)
return true;
else
return false;
}
//把pS所指向的栈出栈一次,并把出栈的元素存入pVal形参所指向的变量中
bool popStack(PSTACK pS, int *pVal)
{
if (empty(pS))
{
return false;
}
else
{
PNODE r = pS->pTop;
pS->pTop = r->pNext;
*pVal = r->data;
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;
}
}
int main()
{
STACK S;
int val;
initStack(&S); //目的是造出一个空栈
pushStack(&S, 1); //压栈
pushStack(&S, 2);
pushStack(&S, 3);
pushStack(&S, 4);
pushStack(&S, 5);
traverseStack(&S); //遍历输出
//clear(&S);
if (popStack(&S, &val))
{
printf("出栈成功,出栈的元素是%d\n",val);
}
else
{
printf("出栈失败\n");
}
system("pause");
return 0;
}
04 队列
定义:
一种可以实现“先进先出”的存储结构
分类:
链式队列 — 用链表实现
静态队列 — 用数组实现 静态队列通常都必须是循环队列
循环队列:
- 静态队列为什么必须是循环队列
front 指向第一个元素 rear 指向最后一个元素的下一个元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vh26bUrn-1606466546217)(C:\Users\52675\AppData\Roaming\Typora\typora-user-images\image-20201125114304865.png)]
-
循环队列需要几个参数来确定
front、rear不同场合有不同含义
-
循环队列各个参数的含义
-
队列初始化
front和rear的值都是零
-
队列非空
front代表的是队列的第一个元素
rear代表的是队列的最后一个有效元素的下一个
-
队列空
front和rear的值相等,但不一定是零
-
-
循环队列入队伪算法
两步完成:
- 将值存入r所代表的位置
- rear = (rear+1) % 数组的长度
-
循环队列出队伪算法
- front = (front+1) % 数组的长度
-
如何判断循环队列是否为空
如果front与rear的值相等
则该队列就一定为空
-
如何判断循环队列是否已满
两种方式:
-
多增加一个表标识参数
-
少用一个元素
if(front == (rear+1) % 数组的长度) 已满 else 不满
-
算法
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
typedef struct Queue
{
int * pBase;
int front;
int rear;
}QUEUE;
//初始化队列
void initQueue(QUEUE *pQ)
{
pQ->pBase = (int *)malloc(sizeof(int) * 6); //指向一个数组
pQ->front = 0;
pQ->rear = 0;
}
//判断队列是否满
bool fullQueue(QUEUE *pQ)
{
if (pQ->front == (pQ->rear + 1) % 6)
return true;
else
return false;
}
//入队
bool enQueue(QUEUE *pQ,int val)
{
if (fullQueue(pQ))
{
return false;
}
else
{
pQ->pBase[pQ->rear] = val;
pQ->rear = (pQ->rear + 1) % 6;
return true;
}
}
//判断队列是否为空
bool emptyQueue(QUEUE *pQ)
{
if (pQ->front == pQ->rear)
return true;
else
return false;
}
//出队
bool outQueue(QUEUE *pQ, int *pVal)
{
if (emptyQueue(pQ))
{
return false;
}
else
{
*pVal = pQ->pBase[pQ->front];
pQ->front = (pQ->front + 1) % 6;
return true;
}
}
//遍历队列
void traverseQueue(QUEUE *pQ)
{
int i = pQ->front;
while (i != pQ->rear)
{
printf("%d ",pQ->pBase[i]);
i = (i + 1) % 6;
}
printf("\n");
return;
}
int main()
{
QUEUE Q;
int val;
initQueue(&Q);
enQueue(&Q, 1);
enQueue(&Q, 2);
enQueue(&Q, 3);
enQueue(&Q, 4);
enQueue(&Q, 5);
enQueue(&Q, 6);
traverseQueue(&Q);
if (outQueue(&Q, &val))
{
printf("出队成功,队列出队的元素是:%d\n",val);
}
else
{
printf("出队失败\n");
}
traverseQueue(&Q);
system("pause");
return 0;
}
05 递归
定义
一个函数自己直接或间接调用自己
#include <stdio.h>
#include <stdlib.h>
void f();
void g();
void k();
void f()
{
printf("FFFF\n");
g();
printf("1111\n");
}
void g()
{
printf("GGGG\n");
k();
printf("2222\n");
}
void k()
{
printf("KKKK\n");
}
int main()
{
f();
return 0;
}
自己调用自己
#include <stdio.h>
void f(int n)
{
if(n == 1)
printf("哈哈哈哈哈哈哈\n");
else
f(n-1);
}
int main()
{
f(3);
return 0;
}
求阶乘
#include <stdio.h>
long f(long n)
{
if(n == 1)
return 1;
else
return n*f(n-1);
}
int main()
{
int val;
printf("请输入n的值:n = ");
scanf("%d",&val);
printf("%d\n",f(val));
return 0;
}
求1+2+…+100
#include <stdio.h>
long sum(int n)
{
if(n == 1)
return 1;
else
return n+sum(n-1);
}
int main()
{
int val;
printf("请输入n的值:n = ");
scanf("%d",&val);
printf("1+2+...+%d = %d",val,sum(val));
return 0;
}
递归为什么可以调用自己?
当一个函数的运行期间调用另一个函数时
- 将所有的实参,返回地址传递给被调用函数保存
- 为被调用的函数的局部变量(形参)分配存储空间
- 将控制转移到被调函数
从被调函数返回到主调函数,系统也要完成三件事:
- 保存被调函数的返回结果
- 释放被调函数所占用的存储空间
- 依照被调函数的返回地址将控制转移到主调函数
递归要满足的三个条件
- 递归必须得有一个明确的中止条件
- 该函数所处理的数据规模必须在递减
- 这个转化必须时可解的
循环和递归
递归:
易于理解
速度慢
存储空间大
循环:
不易理解
速度快
存储空间小
汉诺塔
伪算法
if(n>1)
{
//先把A柱子上的n-1个盘子从A借助C移到B
//再把A柱子上的第n个盘子直接移到C
//最后将B柱子上的n-1个盘子借助A移到C
}
#include <stdio.h>
void hannuota(int n,char A,char B,char C)
{
if(n == 1)
printf("将编号为%d的盘子直接从%c的柱子移动到%c柱子\n",n,A,C);
else
{
/*
先把A柱子上的n-1个盘子从A借助C移到B
再把A柱子上的第n个盘子直接移到C
最后将B柱子上的n-1个盘子借助A移到C
*/
hannuota(n-1,A,C,B);
printf("将编号为%d的盘子直接从%c的柱子移动到%c柱子\n",n,A,C);
hannuota(n-1,B,A,C);
}
}
int main()
{
char ch1 = 'A';
char ch2 = 'B';
char ch3 = 'C';
int n;
printf("请输入要移动盘子的个数");
scanf("%d",&n);
hannuota(n,'A','B','C');
return 0;
}
06 树(非线性结构)
树定义
专业定义:
1. 有且只有一个称为根的节点
2. 有若干个互补相交的子树,这些子树本身也是一棵树
通俗定义:
1. 树是由节点和边组成
2. 每个节点只有1个父节点但可以有多个子节点
3. 但有一个节点例外,该节点没有父节点,此节点称为根节点
专业术语:
节点 父节点 子节点
子孙 堂兄弟
深度:
从根节点到最底层节点的层数称之为深度
根节点是第一层
叶子节点:
没有子节点的节点
非终端节点
实际就是非叶子节点(有子节点的节点)
度
子节点的个数称为度
树分类
一般树
任意一个节点的子节点的个数都不受限制
二叉树
任意一个节点的子节点个数最多两个,且子节点的位置不可更改
分类:
一般二叉树
满二叉树(在不增加树的层数的前提下,无法再多添加一个节点的二叉树就是满二叉树)
完全二叉树(如果只是删除了满二叉树最底层最右边的连续若干个节点,这样形成的二叉树就是完全二叉树)
森林
n个互不相交的树的集合
树的存储
-
二叉树的存储
-
连续存储[完全二叉树]
优点:
- 查找某个节点的父节点和子节点(也包括判断有无子节点)速度很快
缺点:
- 耗用内存空间过大
-
链式存储
-
-
一般树的存储
双亲表示法:求父节点方便
孩子表示法:求子节点方便
双亲孩子表示法: 求父节点和子节点都很方便
二叉树表示法
把一棵普通树转化成二叉树来存储
具体转化方法:
设法保证任意一个节点的左指针域指向它的第一个孩子,右指针域指向它的下一个兄弟
只要满足此条件,就可可以把一个普通树转化成二叉树
一个普通树转化成的二叉树一定没有右子树
-
森林的存储
先把森林转化为二叉树,再存储二叉树
树操作
遍历
-
先序遍历(根,左,右)
- 先访问根节点
- 再先序访问左子树
- 再先序访问右子树
-
中序遍历(左,根,右)
- 中序遍历左子树
- 再访问根节点
- 再中序遍历右子树
-
后序遍历(左,右,根)
- 先中序遍历左子树
- 再中序遍历右子树
- 再再访问根节点
已知两种遍历序列求原始二叉树
通过先序和中序 或者 中序和后序 我们可以还原出原始的二叉树
但是通过先序和后序是无法还原出原始的二叉树的
应用
树是数据库中数据组织一种重要形式
操作系统子父进程的关系本身就是一棵树
面向对象语言中类的继承关系本身就是树
赫夫曼树