/*数据结构与算法
数据结构概述
定义
我们如何把现实中复杂而大量的问题以特定的数据结构
和特定的存储功能保存到主存储器(内存)中,以及在
此基础上实现某个功能而执行的相应操作,这个相应的
操作叫算法
数据结构=个体+个体关系
算法(狭义)=对存储数据的操作
算法
算法是解题的方法和步骤
衡量算法复杂度
1.时间复杂度
大概程序要执行的次数
运行次数最多的步骤的次数
2.空间复杂度
算法执行过程中大概所占用的的内存
3.难易程度
4.健壮性
狭义的算法和数据的存储关系有关
广义的算法与数据的存储关系无关
泛型:
利用某种技术达到的结果就是:不同的存储方式,执行操作时是一样的
数据结构的地位
数据结构是软件中最核心的课程
程序=数据的存储+数据的操作+可以被计算机实现的语言
预备知识:
一、指针
指针的重要性:
指针是c语言的灵魂
定义:
地址
内存单元的编号
从零开始的非负整数
范围:0-ffffffff{0-4g-1}
指针
指针就是地址,地址就是指针
指针变量是存放内存单元地址的变量
指针的本质是一个操作受限的非负整数
分类:
1.基本指针的类型
基本概念
int i=10;
int *p=&i;
1).p存放了i的地址
2).p和i是两个两个完全不同的变量
3).p指向i,*p就是i变量本身
注意
指针变量也是变量,只能存放内存单元的地址
普通变量前不能加*;
常量和表达式前面不能加&;
函数与指针
1).实参为相关变量的地址
2).形参以该变量的类型为类型的指针变量
3).以*形参变量名的方式可以改变数据
2.指针与数组的关系
指针和一维数组
数组名
一维数组是个指针常量
存放的是一维数组第一个元素的地址
它的值是不变的
一维数组名指向的是第一个元素的地址
下标和指针的关系
a[i]<->*(a+i)【a+sizeof(数组类型)】
指针变量的运算
两个指针变量不能加,不能乘,不能除
两个指针变量属于同一数组时可以相减
指针变量可以加减一个整数
数组开辟的是一块连续的存储空间
二、结构体
为什么会出现结构体
为了表示一些复杂的数据,而普通的基本数据变量无法满足要求
什么叫结构体
结构提示用户根据实际需要自己定义的基本数据类型
如何使用结构体
两种方式:
struct Student st={1000,"zhangsan",20};
struct Student * pst=&st;
1.st.sid;
2.pst->sid;<->(*pst).sid;
pst所指向的结构体变量中的sid这个成员
注意事项:
结构体变量不能加减乘除,但是可以赋值;
普通结构体变量和结构体指针变量作为函数传参的问题
三、动态内存的分配与释放
动态构造一维数组
假设构造一int型数组
int * p=(int *)malloc(int len);//A aa=new()<->A * pa=(A*)malloc(sizeof(A));
1.动态分配sizeof(int)*len个字节的内存
2.int * 强制转换地址的类型,以规定每个
数据所占用的空间
3.malloc只有一个int型的形参,表示要求系统分配的字节数
4.malloc函数的功能是请求系统分配sizeof(int)*len个字节
的空间,如果分配成功则返回第一个字节地址,不成功则
返回NULL;
5.(int *)是把第一个字节的地址转化为四个字节的地址,
这样p就指向了第一个四个字节,p+i就指向了第i+1个四个
字节,如果是double则是一次8个字节
6.malloc动态分配的内存必须用free()进行释放,否则一直存在,
因此可以跨函数使用内存
7.指针/地址只占四个字节
四、函数:根据功能设置流程,从而功能,通过试数来找错与优化
模块一:线性结构【把所有的节点用一根直线穿起来】
连续存储【数组】
1.什么叫做数组
元素类型相同
2.数组的优缺点
离散存储【链表】
是后续学习的基础
typedef的用法
定义:
n个节点离散分配
彼此通过指针相连
每个节点只有一个前驱节点,每个节点只有一个后继节点
首节点没有前驱节点,尾节点没有后继节点
专业术语
首节点:
第一个有效的节点
尾节点:
最后一个有效的节点
头结点:
头结点的数据类型和首节点的数据类型相同
第一个有效节点之前的节点
头结点存放有效数字
加头节点的目的是为了简化对链表的操作
头指针:
指向头节点的指针变量,存放头结点的地址
尾指针:
指向尾节点的指针变量
确定一个链表需要几个参数
只需要头指针,通过头指针可以推算出来链 表的所有参数
分类
单链表
双链表
每一个节点有两个指针域
循环链表
能通过任何一个节点找到其他节点
就是尾节点的尾指针指向头结点
尾指针即是头指针
非循环链表
算法
遍历
查找
清空
销毁
求长度
排序
插入节点
删除节点
链表的优缺点
线性结构的常见应用--栈
定义
一种可以实现“先进后出”的存储结构
栈类似箱子
分类
静态栈【数组实现】
动态栈【链表实现】
算法
出栈
入栈
应用
函数调用
中断
表达式求值
内存分配
缓存处理
迷宫
线性结构的常见应用--队列
定义
一种可以实现“先进先出”的存储结构
类似排队
分类
静态队列--用数组实现
1.静态队列为什么必须是循环队列
2.循环队列需要几个参数来确定
需要两个参数来确定
1.front
2.rear
3.循环队列各个参数的含义
建议初学者先记住,然后慢慢体会
1.队列初始化
front和rear的值都是零
2.队列非空
front代表的是队列的第一个元素
rear代表的是队列的最后一个有效元素的下一位、
3.队列空
front和rear相等,但不一定为零
4.循环队列入队伪算法讲解
1.将值存入数组
2.r=(r+1)%数组的长度
5.循环队列出队伪算法讲解
1.f=(f+1)%数组的长度
6.如何判断循环队列是否为空
front==rear
7.如何判断循环队列是否已满
预备知识
front的值可能比rear大
front的值可能比rear小
也可能相等
两种方式
1.多增加一个表标识参数
2.少用一个元素【通常使用】
无论什么时候都是从front到rear
所以当(r+1)%数组长度==f时队列满
8.循环队列的程序演示
链式队列
算法
出队
入队
队列的具体应用:
所有和时间有关的操作都与队列有关
专题:递归
定义:
一个函数直接或间接调用自己
函数调用的过程类似栈的使用
递归的理解:
规模大的问题的解决依赖于规模较小的问题的解决而解决
当规模递减到某种程度,(值可以递增,但是规模必须递
减)当这个规模下问题可以很容易解决时再递推回来,这
个问题就解决了
函数调用:
当在一个函数的运行期间调用另一个函数时,
一、在运行被调函数前,系统需要完成三件事
1.将所有的实际参数,返回地址等信息传递给被调函数保存
2.为被调函数的局部变量分配存储空间
3.将控制转移到被调函数的入口
二、执行函数步骤
三、从被调函数返回主调函数之前,系统要完成三件事
1.保存被调函数的返回结果
2.释放被调函数的存储空间
3.依照被调函数保存的返回地址将控制转移到主调函数
当有多个函数相互调用的时候,按照“后调用先返回”的原则
系统将整个程序运行时所用的数据空间安排到一个栈中,每
当调用一个函数时,就在栈顶分配一个存储区,进行压栈操
作,每当函数退出时,就释放它的存储区,进行出栈操作,
当前运行的函数永远都在栈顶位置
递归需要满足的三个条件
1.递归必须得有一个明确的终止条件
2.该函数所处理的数据规模必须在递减
3.这个转化必须是可解的
递归与循环
递归:
易于理解
速度慢
存储空间大
循环:
不易理解
速度快
存储空间小
举例:
1.求阶乘
2.1+2+3+...+100的和
3.汉诺塔
4.走迷宫
递归的应用
树和森林就是以递归的方式定义的
树和图的很多算法就是以递归实现的
很多数学公式就是以递归的方式定义的
模块二:非线性结构
树
定义
1.有且只有一个称为根的节点
2.有若干个互不相交的子树,这些子树本身就是一棵树
1.树是由节点和边组成
2.每个节点只有一个父节点,但可以有多个子节点
3.但是有一个节点例外,该节点没有父节点,此节点称为根节点
专业术语
节点 父节点 子节点
子孙 堂兄弟
深度:
从根节点到最底层的层数
叶子节点:
没有子节点的节点
非终端节点:
实际就是非叶子节点
度:
子节点的个数
树的度:
含有最多子节点的节点称为树的度
分类
一般树
任意一个节点的个数都不受限制
二叉树
任意一个节点的子节点个数最多两个,且子节点的位置不可改变,即左右节点不可改变位置
分类
一般二叉树
满二叉树
在不增加树的节点的前提下,无法再多添加
完全二叉树
如果只是删除了满二叉树最底层最右边的若干个节点,
这样形成的二叉树就是完全二叉树,只能删最底层的元
素,只能从右向左删
满二叉树一定是完全二叉树
森林
n个互不相交的树的集合
树的存储
二叉树的存储
连续存储【完全二叉树】
优点:
查找或判断某个节点的父节点和子节点非常快
缺点:
耗用内存空间过大
链式存储
通过指针域进行连接
分为四块,三个指针域,一个数据域
一般树的存储
双亲表示法
求父节点方便
存放父节点的下标
孩子表示法
求子节点方便
存放所有子节点的地址
双亲孩子表示法
二叉树表示法
把一个普通树转化称二叉树来存储
具体转化方法:
任意一个节点的左指针指向它的第一个孩子
任意一个节点的右指针指向它的右边第一个兄弟
一个普通树转化成的二叉树一定没有右子树?
左边的是孩子,右边的是兄弟
森林的存储
将几个根节点看成兄弟,然后使用二叉树表示法
树的操作
只要节点非零就是一棵子树
遍历
先序遍历【先访问根节点】
1.访问根节点
2.先序访问左子树
3.先序访问右子树
中序遍历【中间访问根节点】
1.中序访问左子树
2.访问根节点
3.中序访问右子树
后序遍历【最后访问根节点】
1.后序遍历左子树
2.后序访问右子树
3.访问根节点
已知两种遍历序列求原始二叉树
单个的遍历序列无法还原出原始的二叉树
通过先序和后序无法还原出原始的二叉树
只有通过先序和中序或中序和后序才能还原出原始的二叉树
先序:ABCDEFGH
中序:BDCEAFHG
可以求出原始二叉树
可以求出后序:DECBHGFA
中序:BDCEAFHG
后序:DECBHGFA
可以求出原始二叉树
可以求先序:ABCDEFGH
树的应用
树是数据库中数据组织的一种重要形式
操作系统子父进程的关系本身就是一颗树
面向对象语言中类的继承关系本身就是一颗树
赫夫曼树
图
模块三:查找和排序
折半查找
排序:
冒泡
插入
选择
快排
归并排序
模块四:
c++stl
迭代器
begin() 返回一个迭代器,指向第一个元素
end() 返回一个迭代器,指向最后一个元素之后
容器
vector:vector<类型> 名字;
vector<int> ve;初始化
ve.clear();清空ve里的所有元素。
ve.empty();判断ve是否为空,如果是返回true,否则false
ve.size();返回ve的长度。注意这里返回的类型是unsigned int,如果ve是空的ve.size()
ve.pop_back();删除数组里的最后一个元素。
ve.push_back(const value_type &val);作用是在数组后面增加一个元素。括号里填的是ve里装的东西
set:set<类型> 名字;
set<int> se
se.insert(1);插入元素1
if(se.find(1)!=se.end());判断元素是否属于集合
se.erase(1);删除元素1(如果存在)
如何通过迭代器从小到大遍历所有元素:
for (set<string>::iterator i = d.begin(); i != d.end(); i++)
cout << *i << endl;
map:map<类型 类型> 名字;
map<int string> ma;
queue:queue<类型> 名字;
queue<int> qu;
qu.front();获得队首元素
priority_queue:默认优先级从高到低排列
priority_queue<int> pqu;
top();获得队首元素
next_permutation:求全排列,要包含头文件<algorithm>
next_permutation(a,a+2);数组
再次讨论什么是数据结构
数据结构是研究数据的存储与数据的操作的一门学问
数据的存储分为两部分
个体的存储
个体关系的存储
数据存储的核心是个体关系的存储
什么是泛型
同一种逻辑结构,无论该逻辑结构物理存储方式是什么样的
我们都可以对它执行相应的操作
*/
/*1.vector:在使用它时,需要包含头文件vector,#include<vector>.
vector 容器与数组相比其优点在于它能够根据需要随时
自动调整自身的大小以便容下所要放入的元素,提供了许
多的方法来对自身进行操作.*/
/*2.初始化:vector<int> a ; //声明一个int型向量a
vector<int> a(10) ; //声明一个初始大小为10的向量
vector<int> a(10, 1) ; //声明一个初始大小为10且初始值都为1的向量
vector<int> b(a) ; //声明并用向量a初始化向量b
vector<int> b(a.begin(), a.begin()+3) ; //将a向量中从第0个到第2个(共3个)作为向量b的初始值
int n[] = {1, 2, 3, 4, 5} ;
vector<int> a(n, n+5) ; //将数组n的前5个元素作为向量a的初值
vector<int> a(&n[1], &n[4]) ; //将n[1] - n[4]范围内的元素作为向量a的初值*/
/*3.输入输出:vector<int> a(10, 0) ; //大小为10初值为0的向量a
//对其中部分元素进行输入
cin >>a[2] ;
cin >>a[5] ;
cin >>a[6] ;
//全部输出
int i ;
for(i=0; i<a.size(); i++)
cout<<a[i]<<" " ;*/
/*4.迭代器(遍历器):vector<int>::iterator
vector<int>::iterator t ;//先声明才能用——数组名.begin(),数组名.end()。
for(t=a.begin(); t!=a.end(); t++)//都是地址
cout<<*t<<" " ;// *t 为指针的间接访问形式, 意思是访问t所指向的元素值。*/
/*5.vector向量的各种操作
1)a.size() //获取向量a中的元素个数
2)a.empty() //判断向量a是否为空,空为一,不空为零。
3)a.clear() //清空向量a中的元素
4)a.max_size() // 返回容器所能储存的最大的元素数目。
5)a.pop_back() // 删除最后一个数据。
6)a.push_back(数据) // 在尾部加入一个数据。
7)a = b ; //将b向量复制到a向量中
8)insert //插入前面
1.a.insert(a.begin(), 1000); //将1000插入到向量a的起始位置前
2.a.insert(a.begin(), 3, 1000) ;//将1000分别插入到向量元素位置的0-2处(共3个元素)
3.vector<int> a(5, 1) ;
vector<int> b(10) ;
b.insert(b.begin(), a.begin(), a.end()) ;//将a.begin(), a.end()之间的全部元素插入到b.begin()前
9)erase //删除
1.b.erase(b.begin()) ; //将起始位置的元素删除
2.b.erase(b.begin(), b.begin()+3) ; //将(b.begin(), b.begin()+3)之间的元素删除
10)swap
1.b.swap(a);//a向量与b向量进行交换
11)vector<int>::iterator t ;//先声明才能用——数组名.begin(),数组名.end()。
x = find( a.begin( ), a.end( ), 3 );//查找,在向量a中查找a,返回地址如果地址==a.end,则未查找到元素
return 0;
}*/
/*对于vector来说,每一次删除和插入,指针都有可能失效,不要使用过期的iterator*/
/*
1.next_permutation(a,a+n)改变数组为后一个排列(字典序的前后)
2.next_permutation(node,node+n,cmp)可以按照自定义的排序方式cmp进行排序
3.prev_permutation(start,end)求的是当前排列的前一个排列
#include <iostream>
#include <algorithm>//所需头文件
using namespace std;
int main()
{
int n;
cin>>n;
int a[100];
for(int i=0;i<n;i++)
{
cin>>a[i];
}
sort(a,a+n);//需要先从小到大排序(升序排序)
do
{
for(int i=0;i<n;i++)
{
cout<<a[i];
}
cout<<endl;
}while(next_permutation(a,a+n)==1);//如果还有排列的话返回true,否则返回false
return 0;
}*/
数据结构(一)
最新推荐文章于 2022-06-09 15:55:40 发布