数据结构

智能指针 ????

数据结构

逻辑结构

​ 线性:数组 链表

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

​ 非线性:树 图

物理结构

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 队列

定义:

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

分类:

​ 链式队列 — 用链表实现

​ 静态队列 — 用数组实现 静态队列通常都必须是循环队列

循环队列:

  1. 静态队列为什么必须是循环队列

front 指向第一个元素 rear 指向最后一个元素的下一个元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vh26bUrn-1606466546217)(C:\Users\52675\AppData\Roaming\Typora\typora-user-images\image-20201125114304865.png)]

  1. 循环队列需要几个参数来确定

    front、rear不同场合有不同含义

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

    1. 队列初始化

      front和rear的值都是零

    2. 队列非空

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

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

    3. 队列空

      front和rear的值相等,但不一定是零

  3. 循环队列入队伪算法

    两步完成:

    1. 将值存入r所代表的位置
    2. rear = (rear+1) % 数组的长度
  4. 循环队列出队伪算法

    1. front = (front+1) % 数组的长度
  5. 如何判断循环队列是否为空

    如果front与rear的值相等

    则该队列就一定为空

  6. 如何判断循环队列是否已满

    两种方式:

    1. 多增加一个表标识参数

    2. 少用一个元素

      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; 
}

递归为什么可以调用自己?

当一个函数的运行期间调用另一个函数时

  1. 将所有的实参,返回地址传递给被调用函数保存
  2. 为被调用的函数的局部变量(形参)分配存储空间
  3. 将控制转移到被调函数

从被调函数返回到主调函数,系统也要完成三件事:

  1. 保存被调函数的返回结果
  2. 释放被调函数所占用的存储空间
  3. 依照被调函数的返回地址将控制转移到主调函数

递归要满足的三个条件

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

循环和递归

递归:

​ 易于理解

​ 速度慢

​ 存储空间大

循环:

​ 不易理解

​ 速度快

​ 存储空间小

汉诺塔

伪算法

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个互不相交的树的集合

树的存储

  • 二叉树的存储

    • 连续存储[完全二叉树]

      优点:

      • 查找某个节点的父节点和子节点(也包括判断有无子节点)速度很快

      缺点:

      • 耗用内存空间过大
    • 链式存储

  • 一般树的存储

    ​ 双亲表示法:求父节点方便

    ​ 孩子表示法:求子节点方便

    ​ 双亲孩子表示法: 求父节点和子节点都很方便

    ​ 二叉树表示法

    ​ 把一棵普通树转化成二叉树来存储

    ​ 具体转化方法:

    ​ 设法保证任意一个节点的左指针域指向它的第一个孩子,右指针域指向它的下一个兄弟

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

    ​ 一个普通树转化成的二叉树一定没有右子树

  • 森林的存储

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

树操作

遍历

  • 先序遍历(根,左,右)

    • 先访问根节点
    • 再先序访问左子树
    • 再先序访问右子树
  • 中序遍历(左,根,右)

    • 中序遍历左子树
    • 再访问根节点
    • 再中序遍历右子树
  • 后序遍历(左,右,根)

    • 先中序遍历左子树
    • 再中序遍历右子树
    • 再再访问根节点

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

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

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

应用

树是数据库中数据组织一种重要形式

操作系统子父进程的关系本身就是一棵树

面向对象语言中类的继承关系本身就是树

赫夫曼树

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值