数据结构
绪论
0、数据
数据就是能够输入计算机中,并被计算机识别、存储、处理、显示的信息
数据是由数据元素组成
数据元素:是数据的基本单位,数据元素有由若干数据项构成,数据项就是数据元素的最小单位,可以用基本数据类型表示信息的一个属性
1、数据结构的定义
数据结构:研究数据与数据之间的逻辑结构,并研究如何在计算机中存储这种有关系和有关系的数据(存储结构),以及对这些有关系的数据进行操作
2、数据之间的关系分类
分类一
线性关系 (排队)
层次关系 (上下级)
网状关系(地铁线路)
3、数据结构研究内容
研究:逻辑结构、存储结构、数据运算
关系-------逻辑结构
逻辑结构是数据之间的抽象关系
四种逻辑结构:集合结构、线性结构、树形结构、图形结构
集合结构
所有数据元素之间没有任何关系,只不过这些数据元素有共同的属性、特点以及同属于一个整体
线性结构
数据元素之间存在先后顺序。最多1个前驱,一个后继
树形结构
数据元素之间是一种层次关系。一个前驱,n个后继
图形结构
数据元素之间存在多个关系。n个前驱、n个后继
存储--------存储结构
数据逻辑结构在计算机中的映射。就是在计算机中存储数据,并表示出这些数据之间关系
顺序存储---------连续内存空间
将数据元素存储在一段连续的空间中,也就是说这些数据在内存中的存储地址是连续的,只需要知道第一个数据元素的地址,就可以知道其他数据元素的地址。例如数组
将数据元素按照逻辑顺序存储在内存中一段连续的空间,利用内存空间的地址来表示先后顺序
链式存储------------数据的存储空间是随机的
数据元素在内存中的存储地址是随机的,不一定是连续的,(所以不需要一次性全部申请空间,根据需要来申请空间)需要通过在存储数据元素的同时,将下一个数据元素的地址一并存上。这样任然能够表示数据元素之间的关系。
链式存储 : 存储数据元素 + 关系
索引存储
在存储数据元素的同时,还要存储数据的索引在一个索引表中,可以通过索引表得到数据元素之间的关系。例如:电话簿
散列存储
通过数元素中的部分关键字,进行某种运算得到该数据元素的地址。
运算--------数据操作
增、删、该、查
算法
算法定义
算法就是解决某个问题的方法步骤
在计算机中算法是一个有限的语句集合
算法与程序的区别
算法就是解决某个问题的方法步骤,程序是在计算机中以计算机语言具体实现
区别:算法不一定要依赖计算机语言,是一种描述方式。程序是必须要依赖计算机语。算法是有限的,程序可以是无穷的。
算法与数据结构
算法设计:依赖于数据结构的逻辑结构
算法的实现:依赖于数据结构的存储结构
程序=数据结构+算法
算法的好坏判断
- 正确性
- 效率:消耗时间多少、消耗空间多少
- 编程:算法结构好、易于、理解、编写、调试
算法的效率度量
时间复杂度
时间计算:
-
事后统计法
把算法实现,进行运行,得到算法运行消耗的时间。
**缺陷:**依赖于特定的计算机软硬件,进行大量的统计
-
事先估计法
假设所有计算机执行一条指令的时间都是一致的,只需要统计出执行的语句的次数
语句频度:算法中一条语句的重复次数
时间复杂度T(n):所有语句频度之和就是算法的语句执行次数
在循环中一条语句,执行次数不确定,就用问题规模n来表示
通常利用事先估计法来表示算法时间。
计算时间复杂度
-
T(n)表示法:每条语句的语句频度 * 语句条数
-
大O表示法O(n):n代表无穷大,并且取T(n)表达式的最高次幂。如果语句执行次数为常数次,那么O(n)=1;
空间复杂度S(N)
占用空间的大小
一、线性表
线性表概念
多个数据元素的逻辑结构为线性关系,这些数据元素之间存在先后顺序,就叫做线性结构,即是线性表。数组就是一个
线性关系:中有且仅有“第一个数据元素”和一个“最后一个数据元素”,中间的数据元素只有一个前驱和一个后继
在存储线性表时,既需要存储具有线性表的数据元素,也需要存储元素数据之间的关系。
总而言之,线性表就是一种逻辑结构为线性结构关系的逻辑结构
线性表的存储结构
线性表的顺序存储:存储数据元素时,把数据元素按照逻辑关系(先后顺序),依次存储在一段连续的内存空间,借助元素在这段连续空间的地址,来表示数据间的先后顺序,这就是顺序结构
顺序表
顺序表 = 数据元素逻辑结构为线性表 + 存储结构为顺序存储。
数组就是一个顺序表
顺序表的运算
-
添加
-
删除
-
修改
-
查找
-
判断满
-
判断空
-
求表长
-
创建表
-
清空表
顺序表的实现
- 申请一段连续的内存空间来存储数据,通过地址来表示先后关系
- 记录表中元素的个数
顺序存储的缺陷
- 要申请一段连续的内存空间,申请的时候大小就已经固定了,空间不能动态的添加、删除,这样比较占用内存资源
- 插入、删除操作比较麻烦,需要移动整片空间
#include <stdio.h>
#include <stdlib.h>
struct array
{
int num;
//struct book Book[50]; //表示为申请了50个连续的空间,每个元素为book
int no[20]; //定义一个,int类型的数组来存储每一本书的编号
};
//在堆中动态创建结构体,并返回结构体在堆中的首地址
struct array * creat()
{
struct array * p=malloc(sizeof(struct array));
if(p==NULL)
{
printf("内存空间申请失败!\n");
return NULL;
}
p->num=0;
return p;
}
//判断结构体空间是否为空
int empty(struct array *p)
{
if(p->num == 0)
{
printf("空间为空\n");
return 1; //空间为空返回1
}
return 0;
}
//判断是否空间是否满了
int full(struct array *p)
{
if(p->num == 20)
{
printf("空间已满\n"); //空间如果满了返回1
return 1;
}
return 0; //空间如果没满返回0
}
//添加新的元素
void add(struct array * p,int pos,int shu) //传入结构体的首地址和需要添加元素的位置、添加的元素的值
{
//判断空间是否为满了
if(full(p))
{
return ; //如果空间满了,那就自接退出,不操作
}
//判断空间是否为空
if(empty(p)) //空间为空,那么就将插入的数据添加到no数组第0个元素位置
{
p->no[0]=shu;
p->num ++;
return ;
}
//判断插入下标是否越界
if(pos > p->num -1 )
{
printf("现在有%d个数据,下标范围是0~%d\n",p->num,(p->num) -1);
printf("插入的数据下标为%d超出下标范围,将该数据加在最后一个数据元素后面\n",pos);
putchar('\n');
p->no[p->num]=shu; //越界了要将数据添加在最后一个元素后面,而不是覆盖最后一个元素p->no[p->num-1]
p->num ++;
return ; //插入元素下标越界,结束添加功能
}
for(int i = p->num - 1;i >= pos ;i--) //将pos处以及之后的元素后一位,空出来给添加的元素
{
p->no[i+1]=p->no[i];
}
p->no[pos]=shu;
p->num ++;
}
//删除执行位置的数据元素
void del(struct array *p,int pos,int * k)
{
//判断是否为空
if(empty(p))
{
return ;
}
//判断下标是否越界
if(pos >= p->num -1)
{
printf("现在有%d个数据,下标范围是0~%d\n",p->num,(p->num) -1);
printf("删除的数据下标为%d超出下标范围,将k的值赋值为0\n",pos);
putchar('\n');
*k=0;
return ; //插入元素下标越界,结束添加功能
}
*k=p->no[pos];
printf("需要删除下标为%d,数据为%d\n",pos,*k);
for(int i=pos;i< p->num-1;i++) //将pos之后的第一个元素赋值给pos的位置,其他元素一次前移一个位置
{ //检验判断的条件是,将条件带入到式子中,最后一次前移是,p->nos[num-2]=p->nos[num-1];
p->no[i]=p->no[i+1];
}
printf("删除成功\n");
p->num --;
}
//查找数据元素
void reserch(struct array *p,int pos)
{
//判断查看的下标是否越界
if(pos > p->num -1)
{
printf("现在有%d个数据,下标范围是0~%d\n",p->num,(p->num) -1);
printf("查看的数据下标为%d超出下标范围,自接退出查找\n",pos);
putchar('\n');
return ; //插入元素下标越界,结束添加功能
}
printf("查看的数据元素是下标为%d的元素数据%d\n",pos,p->no[pos]);
}
//修改数据元素
void change(struct array *p,int pos,int shu)
{
//判断查看的下标是否越界
if(pos > p->num -1)
{
printf("现在有%d个数据,下标范围是0~%d\n",p->num,(p->num) -1);
printf("查看的数据下标为%d超出下标范围,自接退出修改\n",pos);
putchar('\n');
return ; //插入元素下标越界,结束添加功能
}
printf("修改前:%d\n",p->no[pos]);
p->no[pos]=shu;
printf("修改完成\n");
printf("修改后:%d\n",p->no[pos]);
}
//数据显示
void show(struct array *p)
{
for(int i=0;i<p->num;i++)
{
printf("%d ",p->no[i]);
}
putchar('\n');
}
int main()
{
int k=0;
struct array *p=creat();
show(p);
add(p,10,1);
add(p,10,2);
add(p,10,3);
add(p,10,4);
add(p,10,5);
add(p,10,6);
show(p);
del(p,0,&k);
show(p);
add(p,4,7);
show(p);
reserch(p,5);
change(p,0,8);
show(p);
}
链表
链式存储:是一种不需要连续空间存储数据的数据结构,需要动态存储数据内容和数据之间的先后关系。
总而言之:链表就是一种
链表的概念
链表是一种数据结构,它的逻辑结构为线性表,存储结构为链式存储。数据元素之间的位置是任意的(需要有这个元素才动态申请存储空间),一个数据元素由数据内容+关系组成。其中关系是通过指针来表示,这个指针存储下一个数据元素的地址。
链表 = 线性表 + 链式存储
链表组成
-
头指针:是一个指针,存储链表中第一个元素的地址,方便指名链表的位置,之后可以通过这个指针来访问其他数据元素。
-
首元素节点
真正存储数据的节点
-
头节点
在链表,通常会添加一个不存储数据的节点,就是空节点,作为链表的开始节点,真正存储数据的节点是第二个节点。这样的话就可以解决删除和添加数据元素到首节点位置时,要改变头指针指向。加了头节点的话删除首节点就和删除其他节点一样,只需要改变数据元素中的指针的指向。
链表的创建
链表元素的插入和删除位置固定
链表元素的插入和删除都是第一个元素位置。这时就不需要
利用for循环去找插入位置前一个元素的地址。因为for循环的时间赋复杂度为n,字节添加和删除首节点位置数据元素的操作时间复杂度为1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//声明结构体
struct node
{
char name[20];
struct node *prior ,* next;
};
//常见链表的头节点
struct node * create()
{
struct node * head = malloc(sizeof(struct node));
if(head == NULL)
{
printf("malloc linklist error\n");
return NULL;
}
head->prior = NULL;
head->next = NULL;
return head;
}
//添加数据元素
void insert_list(struct node * head,int pos,char * data)
{
struct node * p = head;
//寻找pos-1的位置地址------------如果选择添加在首节点位置,那么可以不需要写这些
/*int i = 0;
while(i <pos -1 && p->next != NULL)
{
p = p->next;
i++;
}
if(p->next != NULL) //判断P指向的是否为最后一个元素
{
p->next->prior = new;
}*/
struct node * new = malloc(sizeof(struct node ));
new->next = p->next;
p->next = new;
new->prior = p;
strcpy(new->name,data);
}
//判断是否空间没有存储数据元素
int empty(struct node * head)
{
if(head->next == NULL)
{
printf("is empty\n");
return 1;
}
return 0;
}
//删除数据元素
void delete_list(struct node * head,int pos)
{
if(empty(head))
{
return ;
}
struct node * p = head;
/*
/*int i = 0;
while(i < pos - 1 && p->next != NULL) //让指针p指向pos-1这个地址
{
p = p->next;
i++;
}
if(p->next == NULL) /*功能:判断我要删除的pod这个位置的元素是否为空,为空就退出.
前面已经有这个判断条件了,而且此时的指针p已经指向最后一个元素的地址了,为什么这里还要判断一次?
因为;在while中,当p指向pos-1的地址时,i也变为了pos-1,先判断i<pos-1,不满足为假,自接结束循环,不 会在判断p->next!=NULL这个条件
{
printf("pos is not found\n");
return ;
}
*/
struct node * q = p->next;
p->next = q->next;
if(q->next != NULL)
{
q->next->prior = p;
}
printf("delete data is : %s\n",q->name);
free(q);
}
//修改数据元素------通过查询位置编号
void update_pos_list(struct node * head,int pos ,char * newdata)
{
struct node * p = head;
int i = 0;
while(i < pos)
{
p = p->next;
if(p == NULL)
{
printf("error pos\n");
return ;
}
i++;
}
strcpy(p->name,newdata);
}
//修改数据元素----------通过查询数据元素内容
void update_data_list(struct node * head,char * data,char * newdata)
{
/*
struct node * p = head->next;
while(p != NULL)
{
if( strcmp(p->name,data) == 0 )
{
strcpy(p->name,newdata);
}
p = p->next;
}
*/
struct node * p = head;
while(p->next != NULL)
{
p = p->next;
if( strcmp(p->name,data) == 0 )
{
strcpy(p->name,newdata);
}
}
}
//
void show_list(struct node * head)
{
struct node * p = head;
while(p->next != NULL)
{
p = p->next;
printf("%s\t",p->name);
}
printf("\n");
}
int main()
{
struct node * head = create();
delete_list(head,5);
insert_list(head,10,"zhangsan");
insert_list(head,10,"laoliu");
insert_list(head,1,"wangwu");
insert_list(head,1,"laozhang");
show_list(head);
delete_list(head,5);
show_list(head);
delete_list(head,5);
show_list(head);
}
#include <stdio.h>
#include <stdlib.h>
int m=0;
struct node
{
int date;
struct node * next;
};
struct node * creat()
{
struct node * p = malloc(sizeof(struct node));
if(p == NULL)
{
printf("申请空间失败\n");
return NULL; //可以写return head; 吗?
}
p->next = NULL;
return p;
}
void add_list(struct node * head,int pos,int new_date)
{
struct node * p = head;
int i=0;
while(i<pos-1 && p->next != NULL)
{
p = p->next;
i++;
}
struct node * new = malloc(sizeof(struct node ));
new->next = p->next;
p->next = new;
new->date = new_date;
m++;
printf("m=%d\n",m);
return ;
}
void show_list(struct node * head)
{
if(head->next == NULL)
{
printf("没有元素,无法显示\n");
return ;
}
struct node * p = head->next;
while(p != NULL)
{
printf("%d ",p->date);
p = p->next;
}
putchar('\n');
return ;
}
//判断空间为空
int empty(struct node * head)
{
if(head->next == NULL)
{
printf("空间为空\n");
return 1;
}
return 0;
}
//从小到大排序
void paixu(struct node * head)
{
if(empty(head))
{
return ;
}
struct node * p = head->next;
if(p->next == NULL)
{
printf("只有一个元素\n");
printf("%d\n",p->date);
return ;
}
for(int i=0;i<m-1;i++)
{
for(int j=0;j<m-i-1;j++)
{
if(p->date > p->next->date)
{
int a=0;
a = p->date;
p->date = p->next->date;
p->next->date = a;
}
p = p->next;
//printf("此时p指向的地址里面的值为%d\n",p->date);
}
p=head->next;
}
}
int main()
{
struct node * head = creat();
show_list(head);
for(int i=0;i<10;i++)
{
int pos = 0,date = 0;
printf("请输入需要添加的位置和数据\n");
scanf("%d%d",&pos,&date);
add_list(head,pos,date);
}
show_list(head);
paixu(head);
show_list(head);
}
循环链表
循环链表的概念
最后一个数据元素指向的地址为头节点的地址
if(p->next == NULL)
{
p->next = head;
}
//判断空
if(head->next == head)
{
}
//修改头节点的next和prior指向就行
struct node * creat()
{
struct node * head = malloc(sizeof(struct node));
//就不存在指针指向空的情况
head -> next = head;
head -> prior = head;
}
//以后在判断最后一个数据元素的时候只需要判断p->next是否为head,不在是判断为NULL
链表和顺序表的优缺点
线性表优点
-
连续空间存储数据元素,可以通过地址来表示逻辑关系
-
查看和修改数据元素的时候方便,只需要查看下标就行。
//例如查看下标为pos这个元素 p->nos[pos],(前提是pos< p->num-1); //修改下标为pos这个元素 p->nos[pos] = date;
线性表的缺点
- 增加和删除元素,时间复杂度很高,O(n)。原因:
- 1 添加一个元素到pos这个位置,需要利用循环,从p->nos[p->num-1] 这个元素开始,直到p->nos[p->pos]为止,将每一个元素向后移动一位才行。
- 1同理删除pos这个位置的元素,需要利用循环,从p->nos[p->pos+1]这个元素开始,到p->nos[p->num-1]这个元素为止,将每一个元素向前移动一位
链表的优点
-
添加和删除元素方便,时间复杂度为O(1)
//添加一个元素------------默认是头插法,就是每次都是从头结点之后插入元素 struct node * p = head; new->next = p->next; p->next = new; //删除一个元素----------头删法 struct node * p = head; struct node *q = p->next; p->next = q->next;
-
动态申请空间,不需要空间连续,元素多少可以随时变化,要多少空间就开辟多少空间
链表的缺点
-
修改和查询元素时间复杂度O(n)。需要利用循环从头开始取寻找需要修改或者查询的位置。
//修改元素 struct node * p = head; while(p->next != NULL) { p=p->next; p->date = new_date; }
二、栈与队列
栈的概念
栈就是一个只允许固定位置操作的线性表,允许操作的一端叫做栈顶,不允许操作的一端叫做栈底。遵循先进后出的原则。
每当操作(删除或者插入)一个栈顶元素,就会有一个新的栈顶元素。
顺序栈
逻辑结构: 栈
存储结构:顺序存储
顺序栈的概念
开辟一个连续的内存空间,规定一端为不可操作的栈底,将栈顶指向栈底的位置,这样添加数据元素的时候任然满足在栈顶操作,之后每添加一个元素,栈顶就加一,始终保持栈顶指向下一个将要添加的位置。如果想要删除操作,那么只需要将栈顶减1,因为此时的栈顶是指向下一个将要添加元素的位置,而且在栈顶减1后,栈顶指向被删除的元素的地址,下一次如果是添加元素,那么就会添加在当前栈顶的位置,也就是要被删除的元素的地址,这样添加的元素就会覆盖掉之前的要被删除的元素;如果是删除元素,任然需要先将栈顶地址减1,此时栈顶位置已经变了,不会在指向上一次要删除的元素的地址。
#include <stdio.h>
#include <stdlib.h>
struct node
{
int date[10];
int top; //这里是声明一个栈顶指针top,由于数组名加上下标(date+top)就是元素地址,所以没有用int * top的形式
};
//创建一个空栈,里面可以存10个int类型的数
struct node * creat()
{
struct node * p = (struct node *)malloc(sizeof(struct node));
p->top = 0;
return p;
}
int full(struct node *p)
{
if(p->top ==(sizeof(p->date) / sizeof(int)))
{
printf("栈空间已满\n");
return 1;
}
return 0;
}
//判断栈是否为空
int empty(struct node *p)
{
if(p->top == 0)
{
printf("栈空间为空\n");
return 1;
}
return 0;
}
//从栈顶添加元素
void add_stack(struct node * p,int date)
{
if(full(p))
{
return ;
}
p->date[p->top] = date;
p->top ++;
}
//从栈顶出栈
void pop_stack(struct node * p)
{
if(empty(p))
{
return ;
}
p->top --;
printf("需要删除的元素为%d\n",p->date[p->top]);
}
//显示栈顶
void show_top(struct node *p)
{
printf("栈顶存放的元素是:%d\n",p->date[p->top -1]);
return ;
}
//遍历栈中所有元素
void show_stack(struct node * p)
{
int i = p->top -1;
while(i >= 0)
{
printf("%d ",p->date[i]);
i--;
}
putchar('\n');
}
int main()
{
struct node * p = creat();
add_stack(p,10);
add_stack(p,20);
add_stack(p,30);
add_stack(p,40);
add_stack(p,50);
show_top(p);
show_stack(p);
pop_stack(p);
show_stack(p);
}
链式栈
链式栈的逻辑结构为:栈
存储结构为:链式存储
链式栈的栈顶在头节点之后的第一个元素位置,也就是head ->next
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//声明结构体
struct node
{
int date;
struct node * next;
};
//创建空栈------头节点
struct node * creat()
{
struct node * head = (struct node * )malloc(sizeof(struct node));
if(head == NULL)
{
printf("空间申请失败\n");
return NULL;
}
head->next = NULL;
return head;
}
//在栈顶添加元素-------------就是在head->next 指向的地址处进行操作
void add_stack(struct node * head,int date)
{
struct node * new = (struct node *)malloc(sizeof(struct node));
new->next = head->next;
head->next = new;
new->date = date;
}
//判断栈空间是否为空
int empty(struct node *head)
{
if(head->next == NULL)
{
printf("栈空间为空,无法删除\n");
return 1;
}
return 0;
}
//删除元素
void pop_stack(struct node * head)
{
if(empty(head))
{
return ;
}
struct node * q = head->next;
head->next = q->next;
free(q);
}
//遍历栈空间元素
void show_stack(struct node * head)
{
struct node * p = head->next;
while(p->next != NULL)
{
printf("%d ",p->date);
p = p->next;
}
putchar('\n');
}
int main()
{
struct node *head = creat();
pop_stack(head);
add_stack(head,10);
add_stack(head,20);
add_stack(head,30);
add_stack(head,40);
add_stack(head,50);
show_stack(head);
pop_stack(head);
show_stack(head);
}
判断括号匹配问题 -------方式一:利用链式栈
#include <stdio.h>
#include <stdlib.h>
struct node
{
char date;
struct node *next;
};
int main()
{
char str[10];
int i=0;
int num=0;
printf("请输入:\n");
scanf("%s",str);
printf("输入的结果%s\n",str);
while(str[i] != '\0')
{
if(str[i]!='[' || str[i]!='(' || str[i]!='<' || str[i]!='{' )
{
num ++;
}
if(str[i]!=']' || str[i]!='}' || str[i]!='>' || str[i]!=')')
{
num ++;
}
i++;
}
if(num!= 0)
{
printf("不匹配\n");
return 0;
}
struct node * p = malloc(sizeof(struct node ));
p->next = NULL;
for(int i = 0;i < str[i] != '\0';i++ )
{
if(str[i] == '[' || str[i] == '{' || str[i] == '<' || str[i] =='(')
{
struct node *new = malloc(sizeof(struct node));
new->next = p->next ;
p->next = new;
new->date = str[i];
printf("添加%c\n",new->date);
continue;
}
else if(str[i] -1 != p->next->date && str[i] -2 != p->next->date )
{
printf("不匹配\n");
//printf("栈顶的元素%c\n",p->next->date);
break;
}else if(p->next->date == '{' && str[i] == '}') //配对{}
{
struct node * q = p->next;
p->next = q->next;
printf("%c出栈\n",q->date);
free(q);
if(p->next == NULL)
{
printf("元素全部出栈,匹配成功\n");
break;
}
continue;
} //匹对[]
else if(p->next->date == '[' && str[i] == ']')
{
struct node * q = p->next;
p->next = q->next;
printf("%c出栈\n",q->date);
free(q);
if(p->next == NULL)
{
printf("元素全部出栈,匹配成功\n");
break;
}
continue;
} //匹对()
else if(p->next->date == '(' && str[i] == ')')
{
struct node * q = p->next;
p->next = q->next;
printf("%c出栈\n",q->date);
free(q);
if(p->next == NULL)
{
printf("元素全部出栈,匹配成功\n");
break;
}
continue;
}
//匹对<>
else if(p->next->date == '<' && str[i] == '>')
{
struct node * q = p->next;
p->next = q->next;
printf("%c出栈\n",q->date);
free(q);
if(p->next == NULL)
{
printf("元素全部出栈,匹配成功\n");
break;
}
continue;
}
}
}
判断括号匹配问题 -------方式二:利用顺序栈
#include <stdio.h>
#include <stdlib.h>
struct stack
{
int top;
char str[10];
};
int main()
{
char ch[10] ;
struct stack * p = malloc(sizeof(struct stack));
p->top = 0;
printf("输入符号\n");
scanf("%s",p->str);
for(int i=0;ch[i] != '\0';i++)
{
if(p->str[p->top]!= ']' && p->str[p->top] != '}' && p->str[p->top] != ')' && p->str[p->top] != '>')
{
p->str[p->top] = ch[i];
p->top ++;
}else
{
p->top --;
if(ch[i]-1 != p->str[p->top] && ch[i]-2 != p->str[p->top])
{
printf("不匹配\n");
break;
}
}
}
}
入栈出栈顺序
只能对栈顶进行操作。一组数据按照顺序入栈,但是出栈可以随时,就是说还有其他数据没有入栈,在栈中的数据就可以出栈。出栈n个数据之后继续入栈其他元素,中途又可以出栈。这样的话,最终出栈得到的元素序列就不定
队列
一种特殊的线性表,只允许在线性表两端进行操作,插入端称为队尾,删除端叫做队头。满足先进先出原则
链队列
逻辑结构为队列(线性表)
存储结构为链式存储--------------插入端称为队尾,删除端叫做队头。有一个对头指针和一个队尾指针,对头指针指向头节点,队尾指针指向需要添加的元素的前一个元素的地址。
在空链队列是,对头和队尾指针都指向头节点。之后每 添加一个元素都需要将队尾指针指向新添加的元素的地址。
三、树的基本操作
树相关的概念
树是一种描述数据和数据之间的关系、存储、运算的数据结构。是节点数大于等于0的有限节点的集合,树只有一个根节点,其他的节点被分为互不相交的有限个集合,这些集合本身也是一颗树,这些树都是根的子树。
树描述的关系是一种层次关系,或者叫做从属关系
树有层次,最大的层叫做树的层
结点的子结点叫做该节点的度,整个树中最大的节点的度叫做树的度,度为0的节点叫做终端节点
每一个节点最多只有两个子节点的树
树中的每个元素都叫做结点
结点的度:一个结点包含的子树的个数
叶子结点(终端结点):度为0的结点
子结点:结点有度,有子树,子树的根节点就是这个结点子结点父结点
兄弟结点:同属于一个父结点
树的度:在树中,最大结点的度,称为整棵树的度
结点的层次:从整棵树的根结点开始,叫做第一层,根的子结点就是第二层,依次加1
树的深度:树中结点层次最大的叫做树的深度
路径:从一个结点到另一个结点,经过的位置叫做路径
路径的边:
路径长度:经过的边的条数叫做路径长度
有序树:(兄弟结点不能交换顺序)根结点的子树不能交换
顺序树:就是一种一对多的关系,结点只有一个前驱,可以有多个后继
树的存储
链式存储:设计每个结点按照树的度设计指针空间(关系),来存储下一层的子结点的地址
二叉树的概念
对于树而言,存储过于繁琐,操作过于复杂,所以想设计一种简单的树来进行表示操作存储。只需要把普通的树转换为这种简单的二叉树就可以了。
二叉树树中的每个结点最多只有两个子树(两个子结点),且严格区分左右子树,不能交换
-
二叉树的第i层,最多有2^(i-1)个结点
-
深度为k的二叉树,最多有2^k - 1 个结点
-
对于二叉树,叶子结点数n0,度为二的结点数n2,n2 = n0 - 1
-
满二叉树:每层都是2^(i-1)个结点,每层都是最多结点
-
完全二叉树:如果有k层,从第一层到k-1层,每一场都是2^(k-1),第k层,一定是从左至右每个位置都有结点(不一定满),也不一定是必须一左一右两个终端节点,也可以是一个左,但是这个节点就没有兄弟节点了,因为已经空了右节点。
二叉树的存储
在二叉树中,从下标为1开始存储,根结点存储下标为1位置,
当前结点的左孩子存储 2 * i(i就是结点的下标),
结点右孩子存储在 2 * i + 1位置
父节点:当前结点的位置 / 2 ,就是父结点的位置
如果i从0开始,那么
当前结点的左孩子存储 2 * (i+1)-1==2i+1(i就是结点的下标),
结点右孩子存储在 2 * (i + 1) +1 -1 == 2i +2位置
父节点:当前结点的位置 / 2 ,就是父结点的位置
二叉树的遍历
- 顺序栈遍历:先将根节点遍历根节点(根节点出栈),把它左节点入栈,然后右节点入栈,之后右节点处于栈顶,先对器进行出栈,同理将有节点的左右子节点入栈,然后遍历右子节点(出栈),后续同上操作。直到所有节点全部遍历完(出栈)。
- **顺序队列遍历(层次遍历):**先遍历根节点(根节点出栈),
- 先序遍历:根------左子树---------右子树
- 中序遍历:左子树----根-----右子树
- 后序遍历:左子树-------右子树------根
利用先序+中序 或者后序+中序的方式可以还原二叉树结构,得到另外一个遍历顺序的结果
具体方法是:
- 利用先序的第一个元素就是根节点,找到根节点。
- 然后再中序中判断其余元素是根的左子树还是由子树成员
- 继续看先序中的下一个元素,并在中序中找到该元素,然后以它为根,利用左–根–右的顺序判断该元素两边的元素在该元素的左边还是右边
- 继续看先序中的下一个元素,,并在中序中找到该元素,然后以它为根,利用左–根–右的顺序判断该元素两边的元素在该元素的左边还是右边
- 直到全部元素遍历完
哈夫曼树
在树中,权值(比重)大的数据放在二叉树的最上层,权值小的放在二叉树的最下层。
带权路径长度:节点的权值 * 根节点到该节点的边数
带权路径长度就可以反应某个节点访问的难以程度
所有要访问的数据都是叶子节点(就是度为0的节点),其余节点就是这个树所占的权重。
四、图的概念
图的存储
邻接矩阵
邻接链表
五、查找、排序算法
计数排序
计数排序:利用待排序元素中最大的元素的值m,来创建一个m+1这么大的一个数组b,然后按顺序将待排序的元素的值,当做b数组的下标,找到该下标对应的元素,对其加1。如果在待排序元素中一个数出现n次,那么就需要将b数组中该元素值为下标的数加n。待排序元素中没有的数,在数组b中就是0;所有带排序元素都遍历完后,将数组b遍历。利用for循环判断条件是b[ i ] != 0,就打印下标 i, 之后b[ i ] - - ,直到不满足条件
#include <stdio.h>
int main()
{
int a[5] = {4,2,6,4,90};
int max=0;
for(int i = 0; i< sizeof(a) / sizeof(int) ;i++)
{
if(a[i] > max)
{
max = a[i];
}
}
printf("max=%d\n",max);
int n=max+1;
int b[n];
bzero(b,n*4); //关键之处,不能将int b[n];写成int b[n]就不管了,因为此时数组大小是个未知数,这种定义方式无异于int b[];这种方式,都是不允许的。需要利用bzero()函数加数组中的所有元素都赋值为0
for(int i = 0; i < sizeof(a) / sizeof(int); i++)
{
if(a[i] >= 0)
{
b[a[i]]++;
}
}
for(int i = 0; i<sizeof(b) / sizeof(int) ;i++)
{
while(b[i] != 0)
{
printf("%d ",i);
b[i]--;
}
}
putchar('\n');
}
选择排序
一种与冒泡排序很像的排序。他是将第一个元素和该元素之后的每一个元素进行比较,如果被比较的元素大于(小于)比较的元素,那么就交换值的大小,之后被比较元素继续用新的值和之后的元素比较,直到它和所有元素都比完。此时被比较元素一定是所有比较元素中最小的。之后利用该方法,取出第二个元素,然它和它之后的每一一个元素比较,方法一样。
和冒泡拍序不同之处在于,里层循环的条件是从i+1开始,小于带比较元素的个数
#include <stdio.h>
int main()
{
int a[5] = {1,4,6,3,7};
for(int i = 0; i < sizeof(a)/sizeof(int ) - 1; i++)
{
for(int j = i+1; j<sizeof(a) / sizeof(int); j++)
{
if(a[i] > a[j])
{
int b = a[i];
a[i] = a[j];
a[j] = b;
}
}
}
for(int i = 0;i < sizeof(a) / sizeof(int);i++)
{
printf("%d ",a[i]);
}
putchar('\n');
}
冒泡排序
从第零个元素开始,将前一个元素和它后面一个元素进行比较,如果前一个元素大于后一个元素,那么交换位置(交换值),依次类推,直到倒数第二个元素和倒数第一个元素比较完,此时最后一个元素一定是所有元素中最大的。之后在进行第二轮,从第零个元素开始,前一个元素和它后面一个元素进行比较,如果前一个元素大于后一个元素,那么交换位置(交换值),依次类推,直到倒数第二个元素和倒数第一个元素比较完(为了减小时间复杂度,需要减i
),此时最后一个元素一定是所有元素中最大的
#include <stdio.h>
int main()
{
int a[5] = {2,6,3,5,1};
for(int i =0;i<sizeof(a) / sizeof(int) -1 ;i++) //有N个数需要排序,那么就需要循环N-1次
{
for(int j = 0;j < sizeof(a) / sizeof(int) -1 -i; j++)
{
if(a[j] > a[j+1])
{
int b = a[j];
a[j] = a[j+1];
a[j+1] = b;
}
}
}
for(int i = 0; i < sizeof(a) / sizeof(int) ; i++)
{
printf("%d ",a[i]);
}
putchar('\n');
}
插入排序
#include <stdio.h>
int main()
{
int a[5] = {2,1,4,23,5};
for(int i = 1;i < sizeof(a) / sizeof(int );i++)
{
int temp = a[i];
int j = i;
while(j >0)
{
if(a[j-1] > temp)
{
a[j] = a[j-1];
}else
{
break;
}
j--;
}
a[j] = temp;
}
for(int i=0 ; i<sizeof(a) / sizeof(int);i++)
{
printf("%d ",a[i]);
}
putchar('\n');
}
折半查找
#include <stdio.h>
int main()
{
int a[10] = {1,4,6,2,7,3,8,4,5,3};
int i = 0,j = 9;
int mid = (i + j) / 2;
int num =0;
scanf("%d",&num);
while(i <= j)
{
if(num == a[mid])
{
printf("找到,位置为%d\n",mid);
break;
}
else if(num > a[mid])
{
i = mid +1;
}else
{
j = mid -1;
}
mid = (i + j) / 2;
}
}