赞
踩
目录
数据:数据是信息的载体,是描述客观事物属性的数、字符、及所有能输入到计算机中并能被计算机程序所识别和处理的符号的集合。数据是计算机程序加工的原料
早期计算机处理的数据(只能处理纯数值型问题)
现代计算机可以处理非数值型问题
对于非数值型的问题:
1、我们关心每个个体的具体信息
2、我们还关心个体之间的关系
数据元素--描述一个个体
数据元素、数据项:数据元素是数据的基本单位,通过作为一个整体进行考虑和处理。一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位
数据对象:是具有相同性质的数据元素的集合,是数据的一个子集
数据结构相互之间存在一种或多种特定关系的数据元素的集合
同一个数据对象里的数据元素可以组成不同的数据结构
数据结构三要素:逻辑结构、数据的运算、存储结构(物理结构)
程序=数据结构(如何用数据正确地描述现实世界的问题,并存入计算机)+算法(如何高效地处理这些数据,以解决实际问题)
算法(Algorithm)是特定问题求解步骤的一种描述,它是指令的有限序列,其中的每条指令表示一个或多个操作
Step1:扫描5个元素,找到年龄最小的一个元素,插入到第一个位置
Step2:扫描剩下的4个元素,找到年龄最小的一个元素,插入到第二个位置
。。。。依次执行结果如下:(该算法不具备确定性,不能称之为算法)
有穷性:一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成。(算法必须是有穷的,而程序可以是无穷的)
算法是用有限步骤解决某种特定的问题
微信是程序而不是算法
确定性:算法中每条指令必须有确切的含义,对于相同的输入只能得出相同的输出
可行性:算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现(该方案必须能用计算机代码来实现)
输入:一个算法中有零个或多个输入,这些输入取自于某个特定的对象的集合。
输出:一个算法有一个或多个输出,这些输出与输入有着某种特定关系的量
正确性:算法应能够正确地解决问题
可读性:算法应具有良好的可读性,以帮助人们理解(写注释)
健壮性:输入非法数据时,算法能当适应地做出反应或进行处理,而不会产生莫名其妙的结果
高效率(花费的时间少时间复杂度低)与低存储需求(不费内存,空间复杂度低)
一个算法的时间复杂度只考虑最高阶的部分,并且去掉系数
加法规则:多项相加只保留最高阶的项并且系数变为1
乘法规则:多项相乘,都保留
时间复杂度排序(当n趋近于无穷时) 常对幂指阶,只考虑阶数,用大O记法表示 注意:
顺序执行的代码只会影响常数项,可以忽略
只需挑循环中的一个基本操作分析它的执行次数与n的关系即可
如果有多层嵌套循环,只需要关注最深层循环循环了几次
练习
最坏时间复杂度:最坏情况下算法的时间复杂度
平均时间复杂度:所有输入示例等概率出现的情况下,算法的期望运行时间
最好时间复杂度:最好情况下算法的时间复杂度
一般只考虑最坏和平均情况下
这种叫做算法原地工作--算法所需内存空间为常量
在调用函数中没有出现数组(长度与传递的参数有关)的定义情况下,空间复杂度=递归调用的深度
线性表是指相同的数据类型的n个(n>=0)个数据元素的有限序列,其中n为表长,当n=0时线性表是个空表,若用L命名线性表,则一般表示为L=(a1,a2,.....ai,ai+1,...an)
ai是线性表中的“第i个”元素线性表中的位序
a1是表头元素;an是表尾元素
除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且只有一个直接后继
Tips:
1、对数据的操作--创销、增删改查
2、C语言函数的定义--<返回值类型> 函数名(<参数1类型> 参数1,<参数2类型> 参数2....)
3、实际开发中,可根据实际需求定义其他的基本操作
4、函数名和参数的形式、命名都可以改变,注意命名要注意可读性
5、什么时候要传入引用"&"--对参数的修改结果需要“带回来”
顺序表--用顺序存储的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系又存储单元的邻接关系来体现。
注意:ElemType是数据类型,也可以是结构体类型
静态分配
- #define MaxSize 10 //定义最大长度
- typedef struct{
- ElemType data[MaxSize]; //用静态的“数组”存放数据元素
- int length; //顺序表的当前长度
- }SqList;//顺序表的类型定义(静态分配方式)
动态分配
- #define InitSIZE 10//顺序表的初始化长度
- typedef struct{
- ElemType *data;//指示动态分配数组的指针
- int MaxSize;//顺序表的最大容量
- int length;//顺序表的当前长度
- }SqlList;//顺序表的类型定义(动态分配)
malloc()函数--动态分配内存函数,malloc函数返回一个指针,需要强制转型为你定义的数据元素类型指针,malloc函数的参数,指明要分配多大的连续内存空间
L.data=(ElemType*)malloc(sizeof(ElemType)*InitSize);
free()释放空间函数
初始化顺序表
- void InitList(SeqList &L){
- //用malloc函数申请一片连续的存储空间
- L.data=(int *)malloc(InitSize*sizeof(int));
- L.length=0;
- L.MaxSize=InitSize;
- }
增加动态数组的长度
- //增加动态数组的长度
- void IncreaseSize(SeqList &L, int len){
- int *p=L.data;
- L.data=(int *)malloc((L.MaxSize+len)*sizeof(int));
- for (int i=0;i<L.length;i++){
- L.data[i]=p[i];//将数据复制到新区域 ,时间开销较大
- }
- L.MaxSize=L.MaxSize+len;//顺序表最大长度增加
- free(p)//释放原来的内存空间
- }
realloc函数也可以实现,但建议使用malloc和free更能理解背后过程。
顺序表的特点:
随机访问,即可以在O(1)时间内找到第i个元素
存储密度高,每个节点只存储数据元素
拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度比较高)
插入和删除不方便,需要移动大量元素
插入:
- bool ListInsert(SqList &L, int i, int e){
- if(i<1||i>L.length+1)//判断i是否有效
- return false;
- if(L.length>=L.MaxSize)//当前存储空间已满,不能插入
- return false;
- for(int j=L.length;j>=i;j--)//将i个元素往后移
- L.data[j]=L.data[j-1];
- L.data[i-1]=e;//插入i
- L.length++;//长度加1
- return true;
- }
删除:
- bool ListDelete(SqList &L,int i,int &e){
- if(i<1||i>L.length)
- return false;
- e=L.data[i-1]
- for(int j=i;j<L.length;j++)
- L.data[j-1]=L.data[j];
- L.length--;
- return true;
- }
按位查找
- ElemType GetElem(SeqList L,int i){
- return L.data[i-1]
- }
- //由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第个
- //元素--"随机存取"的特性
按值查找
- //在顺序表中L查找第一个元素值等于e的元素,并返回其位序
- int LocateElem(SeqList L,ElemType e){
- for(int i=0;i<L.length;i++){
- if(L.data[i]==e)
- return i+1;
- return 0;
- }
每个节点除了存储数据元素之外,还存储下一个节点的指针
优点:不要求大片连续的空间,改变容量方便
缺点:不可随机存取,要耗费一定空间存放指针
不 带头节点的单链表实现
带头节点的单链表实现
判断单链表是否为空
不带头节点:L==NULL
带头节点:L->next==NULL
按位序插入
带头节点:(平均时间复杂度为o(n))
- //在第i个位置插入元素e(带头节点)
- bool ListInsert(LinkList &L,int i, ElemType e){
- if(i<1)//位置不合法
- return false;
- LNode *p;//声明一个节点指针
- int j=0;
- while(p!=null&&j<i-1){//循环找到第i-1个节点
- p=p->next;
- j++;
- }
- if(p==null)//位置不合法
- return false;
- //位置合法,为节点分配内存
- LNode *s=(ElemType *)malloc(sizeof(LNode));
- s->next=p->next;
- s->data=e;
- p-next=s;
- return true;
- }
不带头节点:
- bool ListInsert(LinList &L,int i,ElemType e){
- if(i<1)
- return false;
- if(i==1){//如果没有头结点,i==1时为特殊情况
- LNode *s=(ElemType *)malloc(sizeof(LNode));
- s->data=e;
- s->next=L;
- L=s;//头指针指向新的节点
- return true;
- }
- //如果不是特殊情况与上面的带头节点的代码一致,此处省略,只需j=1即可
- }
指定节点的后插操作
- //后插操作:在p结点之后插入元素e
- bool InsertNextNode(LNode *P,ElemType e){
- if(p==null)
- return false;
- LNode *s=(LNode *)malloc(sizeof(LNode));
- if(s==null)//内存分配失败
- return false;
- s->data=e;//用结点s保存数据元素e
- s->next=p->next;
- p->next=s;//将节点s连接到p之后
- return true;
- }
指定节点的前插操作(时间复杂度O(1))
- //前插操作:在p结点之前插入结点s
- bool InsertPriorNode (LNode *p,LNode *s){
- if(p==null||s==null)
- return false;
- s->next=p->next;
- p->next=s;//s连到p之后
- ElemType temp=p->data;//交换数据域部分
- p->data=s->data;
- s->data=temp;
- return true;
- }
按位序删除(带头节点、时间复杂度O(n))
- bool ListDelete(LinkList &L,int i, ElmeType &e){
- if(i<1)
- return false;
- LNode *p;//指针p指向当前扫描到的结点
- int j=0;//当前p指向的是第几个结点
- p=L;//L只想头结点,头结点是第0个节点(不存储数据)
- while(p!=null&&j<i-1){
- p=p->next;
- j++;
- }
- if(p==null)//i值不合法
- return false;
- if(p->next==null)
- return false;
- LNode *q=p->next;//令q指向被删除结点
- e=q->data;//用e返回元素的值
- p->next=q->next;//将*q结点从链中断开
- free(q)//释放结点的存储空间
- return true;//删除成功
- }
指定结点的删除(此方法不能解决删除最后一个结点)
- //删除指定节点p
- bool DeleteNode(LNode *p){
- if(p==null)
- return false;
- LNode *q=p->next;//令q指向*p的后继节点
- p->data=p->next->data;//和后继节点交换数据域
- p->next=q->next;//将*p节点从链中断开
- free(q);//释放后继节点的存储空间
- return true;
- }
按位查找
- //按位查找,返回第i个元素(带头结点)
- LNode * GetElem(LinkList L, int i){
- if(i<0)
- return null;
- LNode *p;//指针p指向当前扫描到的结点
- int j=0;//当前p指向的是第几个结点
- p=L;//L指向头结点,头结点是第0个结点(不存数据)
- while(p!=null && j<i){//循环找到第i个结点
- p=p->next;
- j++;
- }
- return p;
- }
按值查找
- //按值查找,找到数据域==e的结点
- LNode * LocateElem(LinkList L,ElemType e){
- LNode *p=L->next;
- //从第一个结点开始查找数据域为e的结点
- while(p!=null&&p-data!=e){
- p=p->next;
- }
- return p;//找到后返回该结点指针,否则返回null
- }
求单链表的长度
- //求表的长度
- int Length(LinkList L){
- int len=0;//统计表长
- LNode *p=L;
- while(p->next !=null){
- p=p->next;
- len++;
- }
- return len;
- }
尾插法建立
- LinkList List_Taillnsert(LinkList &L){//正向建立单链表
- int x;//设ElemType为整型
- L=(LinkList)malloc(sizeof(LNode));//建立头结点
- LNode *s,*r=L;//r为表尾指针
- scanf("%d",&x);//输入结点的值
- while(x!=9999){//输入9999表示结束
- s=(LNode *)malloc(sizeof(LNode));
- s->data=x;
- r->next=s;
- r=s;
- scanf("%d",&x);//r指向新的表尾结点
- }
- r->next=null;//尾结点指针置空
- return L;
- }
头插法建立
重要应用!!!链表的重置
初始化头结点的时候要让头结点指向Null
- LinkList List_HeadInsert(LinkList &L){//逆向建立单链表
- LNode *s;
- int x;
- L=(LinkList)malloc(sizeof(LNode));//创建头结点
- L->next=null;
- scanf("%d",&x);//输入结点的值
- while(x!=9999){//输入9999表示结束
- s=(LNode *)malloc(sizeof(LNode))//创建新结点
- s-data=x;
- s-next=L->next;
- L->next=s;//将新结点插入表中,L为头指针
- scanf("%d",&x);
- }
- return L;
- }
- typedef struct DNode{
- ElemType data;//数据域
- struct DNode* prior;//前指针
- struct DNode* next;//后指针
- }DNode,*DLinkList
- //初始化双链表
- bool InitDlinkList(DLinkList &L){
- L=(DNode *)malloc(sizeOf(DNode));//分配头结点
- if(L==null){
- return false;//分配内存失败
- }
- L->prior=NULL;//头结点的前指针永远指向null
- L->next=NULL;//头结点的尾指针初始化的时候指向null;
- return true;
- }
DLinkList<=>DNode *
- //判断双链表是否为空
- bool Empty(DLinkList L){
- if(L->next==null){
- return true;
- }
- else
- return false;
- }
后插法
- //在p结点之后插入s结点
- bool InsertNextDNode(DNode *p,DNode *s){
- if(p==null||s==null)
- return false;
- s->next=s->next;
- if(p->next!=null)//如果p结点有后继结点
- p->next->prior=s;
- s->prior=p;
- p->next=s;
- return true;
- }
在双链表中,其他的任何操作由于每个结点都有前驱结点,所以都可以转化成后插法插入结点
注意:修改指针的顺序
- //删除p结点的后继结点
- bool DeleteNextDNode(DNode *p){
- if(p==null||p->next==null)//p为空后p没有后继结点
- return false;
- DNode *q=p->next//找到p的后继结点q
- p->next=q->next;
- if(q->next!=null){//q不是最后一个结点
- q->next->prior=p;
- }
- free(q);//释放空间
- return true;
- }
其实就是循环这个双链表删除双链表的后继结点直到双链表为空(循环内调用DeleteNextDNode())
- void DestoryList(DLinkList &L){
- //循环释放各个数据结点
- while(L->next!=null)
- DeleteNextDNode(L);
- free(L)//释放头结点
- L=null;//头指针指向null
- }
//向后遍历
while(p!=null){
//对p结点做相应处理,如打印
p=p->next;
}
//向前遍历
while(p!=null){
//对结点p做相应处理
p=p->prior;
}
向前遍历跳过头结点
while(p->prior!=null){
//对结点p做相应处理
p=p->prior;
}
双链表不可随机存取、按位查找(在循环里面加个计数器即可)、按值查找操作(循环里面对p指针进行对比即可)都只能用遍历的方式实现。事件复杂度O(n)
- typedef struct LNode{//定义单链表结点类型
- ElemType data;//每个结点存放一个数据元素
- struct LNode *next;//指针指向下一个结点
- }LNode,*LinkList
初始化一个循环单链表
- bool InitList(LinkList &L){
- L=(LNode *)malloc(sizeof(LNode));//分配一个头结点
- if(L==NULL)
- return false;
- L->next=L;//头结点next指向头结点
- return true;
- }
判断循环单链表是否为空
- bool Empty(LinkList L){
- if(L->next==L)
- return true;
- else
- return false;
- }
- //判断结点p是否为循环单链表的表尾结点
- bool isTail(LinkList L,LNode *p){
- if(p->next==L)
- return true;
- eles
- return false;
- }
如果经常对链表的操作都是在头部和尾部,可以使用单链表
存取(读写)方式
顺序表可以顺序存取,也可以随机存取,链表只能从表头顺序存取元素
逻辑结构与物理结构
顺序表和单链表的逻辑结构都是线性表,逻辑上相邻。顺序表的物理结构中各元素也是相邻,单链表中的各元素在物理上不一定相邻。
查找、插入和删除
对于按值操作:当顺序表无序的时候,顺序表和单链表的时间复杂度都是O(n),但是当顺序表有序的时候,顺序表的时间复杂度是O(log2n),原因是当顺序表有序的时候可以采用一些算法实现。
对于按序号查找:顺序表支持随机存取,时间复杂度是O(1),而链表的时间复杂度还是O(n)。
当插入和删除的时候,顺序表需要移动大量元素,代价较大;单链表只需要修改指针即可。
空间分分配
顺序表在静态存储的时候,存储空间不变;动态存储的时候虽然存储空间可变,但是需要移动大量元素,代价较大。
单链表只要在内存空间有空闲的时候,都可以进行存储。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。