数据结构的基本概念
数据结构三要素: 1.逻辑结构 2. 存储结构(物理)3. 数据的运算
算法的基本概念
- 程序=数据结构+算法
- 算法必须得保证有限时间内能执行完当前问题,无论执行的结果对错
- 若程序不断输入,算法不断解决,程序运行的时间可以是无穷的,但每一次算法解决问题的时间是有穷的
算法的时间复杂度
- 忽略常数项和系数,只关注数量级
- 一般最好时间复杂度不做参考。考虑平均和最坏的情况
- 巴啦啦能量—常对幂指阶
算法的空间复杂度
线性表
线性表为一种逻辑结构(指具有线性序列)
- 定义:线性表为具有相同(每个数据元素所占空间一样大)数据类型的n个数据元素的有限序列(有次序)
eg.所有整数按递增次序排列,不是线性表。—因为线性表是有限序列
+++
-
线性表的基本操作:
-
InitList(&L)
创:初始化表,为其分配空间 -
DestroyList(&L)
销毁表,释放空间 -
ListInsert(&L,i,e)
插入元素 -
ListDelete(&L,i,&e)
删除元素 -
LocateElem(L,e)
按值查找 -
GetElem(L,i)
按位查找 -
Length(L)
求表长 -
PrintList(L)
输出表 -
Empty(L)
判空
创销、增删改查
函数名可改,但如上名字更规范,改卷更合适
顺序表
顺序存储的线性表
静态分配的顺序表
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];//Elemtype可以是int/struct..类型
int length;
}SqList;
容量大小不可变
动态分配的顺序表
#define InitSize 10//顺序表的初始长度
typedef struct{
ElemType *data;//Elemtype可以是int/struct..类型
int MaxSize;//顺序表的最大容量
int length;//顺序表的当前长度
}SqList;//顺序表的类型定义
动态申请空间(malloc&realloc)
malloc函数–申请一块连续空间
L.data=(ElemType *)malloc(sizeof(ElemType)*InitSize);
\\需要进行强制类型转换
释放内存空间:
free函数
示例1.实现顺序表的初始化、输入、扩大动态数组
#include<stdio.h>
#include<stdlib.h>
#define InitSize 10//顺序表的初始长度
typedef struct {
int *data;//Elemtype可以是int/struct..类型
int MaxSize;//顺序表的最大容量
int length;//顺序表的当前长度
} SqList; //顺序表的类型定义
void InitList(SqList &L);
void IncreaseSize(SqList &L,int add);
int in(SqList &L);
int main() {
SqList L;
InitList(L);
L.length = in(L);
int add;
scanf("%d", &add);
IncreaseSize(L, add); //增加顺序表的长度
return 0;
}
void InitList(SqList &L) { //初始化顺序表
L.data = (int *)malloc(sizeof(int) * InitSize);
L.MaxSize = InitSize;
L.length = 0;
}
int in(SqList &L) { //输入顺序表
int i;
for (i = 0; i < L.MaxSize; i++)
scanf("%d", &L.data[i]);
return i;
}
void IncreaseSize(SqList &L,int add){
int *p=L.data;//int *类型
L.data=(int *)malloc(sizeof(int)*(L.MaxSize+add));//开辟一块新内存
for(int i=0;i<L.length;i++){
L.data[i]=p[i];//将原数据复制到新区域
}
L.MaxSize+=add;
free(p);//释放原内存
}
总结–顺序表的特点:
- 可随机访问,即可在O(1)时间内找到第i个元素
- 存储密度高,每个节点只存储数据元素(链表存储密度较低)
- 拓展容量不方便(静态不允许。动态麻烦,时间复杂度高)
- 插入删除不方便,需要移动大量元素
1.顺序表的插入(静态分配情况下)
#include<stdio.h>
#include<stdlib.h>
#define MaxSize 10//顺序表的初始长度
typedef struct {
int data[MaxSize];
int length;//顺序表的当前长度
} SqList; //顺序表的类型定义
void input(SqList &L, int l);
bool ListInsert(SqList &L, int i, int e);
void output(SqList &L,int l);
int main() {
SqList L;
scanf("%d", &L.length);
input(L, L.length);
if (ListInsert(L, 3, 16)) {
printf("已向第3个位序插入元素16\n");
} else {
printf("操作不合法\n");
}
output(L,L.length);
return 0;
}
void input(SqList &L, int l) { //输入顺序表
for (int i = 0; i < l; i++)
scanf("%d", &L.data[i]);
}
bool ListInsert(SqList &L, int i, int e) { //在数组第i-1个下标插入e
if (i < 1 || i > L.length)//不合法位置
return false;
if (L.length >= MaxSize)//顺序表已满
return false;
for (int j = L.length; j >= i; j--) {
L.data[j] = L.data[j - 1];
}
L.data[i - 1] = e;
L.length++;//易漏
return true;
}
void output(SqList &L,int l){
for(int i=0;i<l;i++)
printf("%d ",L.data[i]);
}
2.顺序表的删除(静态分配情况下)
#include<stdio.h>
#include<stdlib.h>
#define MaxSize 10//顺序表的初始长度
typedef struct {
int data[MaxSize];
int length;//顺序表的当前长度
} SqList; //顺序表的类型定义
void input(SqList &L, int l);
bool ListDelete(SqList &L, int i, int &e);
void output(SqList &L, int l);
int main() {
SqList L;
scanf("%d", &L.length);
input(L, L.length);
int m=-1;
if (ListDelete(L, 3, m)) {
printf("已删除第3个位序\n");
} else {
printf("操作不合法,删除失败\n");
}
printf("%d\n", m);
output(L, L.length);
return 0;
}
void input(SqList &L, int l) { //输入顺序表
for (int i = 0; i < l; i++)
scanf("%d", &L.data[i]);
}
bool ListDelete(SqList &L, int i, int &e) { //在删除数组第i-1个下标对应的元素,并将其返回给e
if (i < 1 || i > L.length)//不合法位置
return false;
e = L.data[i - 1];
for (int j = i-1; j <= L.length - 2; j++) {
L.data[j] = L.data[j + 1];
}
L.length--;//易漏
return true;
}
void output(SqList &L, int l) {
for (int i = 0; i < l; i++)
printf("%d ", L.data[i]);
}
顺序表的查找
按位查找
1.静态分配(略)
2.动态分配
#define InitSize 10
typedef struct{
ElemType *data;
int MaxSize;//最大容量
int length;//当前长度
}SqList;
ElemType GetElem(SqList L,int i){//查找第i个位序的元素
return L.data[i-1];
}
按值查找
基本思想:==遍历判等
例外–结构体查找
判断结构体a,b相等,不可用==
需挨个判等
错误示范
typedef struct l{
int num;
int people;
}Customer;
void test(){
Customer a;
a.num=1;
a.people=2;
Customer b;
b.num=1;
b.people=2;
if(a==b){//甚至无法通过编译
printf("相等");
}
}
链表
链式存储的线性表
单链表
单链表的定义
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
LNode *L;//声明一个指向单链表第一个结点的指针
或
LinkList L;//等效,代码可读性更强
LNode *
强调是一个结点
LinkList
强调是个单链表
例1.获取链表中第i个结点
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
LNode * GetElem(LinkList L,int i){//为LinkList,强调传入的是一个单链表
int j=1;
LNode *p=L->next;
if(i==0)
return L;
else if(i<1)
return NULL;
else {
while(p!=NULL&&j<i){
p=p->next;
j++;
}
}
return p;//函数类型为LNode *强调返回的是一个结点
}
不带头结点的单链表
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
bool InitList(LinkList &L){//要用引用类型 **重点理解
L=NULL;//空表,暂时无结点
return true;
}
void test(){
LinkList L;//声明一个指向单链表的指针
InitList(L);
}
重点理解:
LinkList L 本身为结构体指针
不用&传入:改变 L指针指向的地址所存储的元素
用&传入:可改变L指针指向的地址
LinkList L
和LinkList &L
都可改变链表内部的指向
区别在于,加了&可改变头指针的指向
类比
int b=1;
int *a=&b;
f1(a);
void f1(int *x){
*x=2;
}//*a=b=2
int b=1;
int *a=&b;
f2(a);
void f2((int *)&a){
a=NULL;
}//b=1,a=NULL
带头结点的单链表
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
bool InitList(LinkList &L){//要用引用类型 **重点理解
L=(LNode *)malloc(sizeof(LNode));//分配一个头结点,头结点不存储数据
if(L==NULL){
printf("内存不足,分配失败");
return false;
}
else
L->next=NULL;//头结点后暂无其他结点
return true;
}
void test(){
LinkList L;//声明一个指向单链表的指针
InitList(L);
}
在第i个位置插入e(带头结点)
bool ListInsert(LinkList &L,int i,int e){//在第i个位置插入元素e
if(i<1);
return false;
LNode *p;
p=L;//L指向头结点,头结点为第0个结点
int j=0;
while(p!=NULL&&j<i){
p=p->next;
j++;
}
if(p==NULL)//i的值大于原链表长度
retrun false;
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;//插入成功
}
有头结点链表好处:可以在第1个位置插入元素
在第i个位置插入e(不带头结点)
bool ListInsert(LinkList &L,int i,int e){//在第i个位置插入元素e
if(i<1);
return false;
if(i==1){
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=L;
L=s;//头指针指向新结点,改变L的指向
return true;
}
LNode *p;
p=L;//L指向头结点,头结点为第0个结点
int j=0;
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
if(p==NULL)//i的值大于原链表长度
retrun false;
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;//插入成功
}
从此默认带头结点
指定结点的前插(带头结点)
bool InsertPriorNode(LNode *p,ELemType e){//无需改变L的指向,用LinkList p
if(p==NULL)
return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
if(s==NULL)//内存分配失败
return false;
s->next=p->next;
s->data=p->data;
p->next=s;
p->data=e;
return true;
}
按位序删除(带头结点)
并带回删除结点数据
bool ListDelete(LinkList &L,int i,ElemType &e){
if(i<1)
return false;
LNode *p;
p=L;//L指向头结点
int j=0;
while(p!=NULL&&j<i-1){//找到第i-1个结点
p=p->next;
j++;
}
if(p==NULL||p->next==NULL)//i值超过原链表长度 或 第i-1个结点后无其他结点
return false;
LNode *q;
q=p->next;
e=q->data;//用e返回被删除元素数据
p->next=q->next;
free(q);
return true;
}
指定结点的删除
bool DeleteNode(LNode *p){
if(p==NULL)
return false;
LNode *q=p->next;
p->data=q->data;
p->next=q->next;
free(q);
return true;
}
//若p为最后一个结点,只能从表头依次寻找p的前驱
按位查找
O(n)
LNode *GetElem(LinkList L,int i){
if(i<0)
return NULL;
LNode *p=L;
int j=0;
while(p!=NULL&&j<i){//找到第i个结点
p=p->next;
j++;
}
return p;
}
按值查找
O(n)
LNode *LocateElem(LinkList L,ELemType e){
LNode *p=L->next;
while(p!=NULL&&p->data!=e){
p=p->next;
}
return p;
}
当不能找到对应e的结点时,返回NULL
求表长
O(n)
int length(LinkList L){
int len=0;
LNode *p=L;
while(p->next!=NULL){
p=p->next;
len++;
}
return len;
}
单链表的建立
- 初始化一个单链表
- 每次取一个数据元素,插入表尾(尾插法)、表头(头插法)
初始化一个单链表
bool InitList(LinkList &L){
L=(LNode *)malloc(sizeof(LNode));//分配一个头结点
if(L=NULL)//内存不足,分配失败
return false;
L->next=NULL;
return true;
}
尾插法建立单链表
- 初始化单链表
- 设置变量length记录链表长度
时间复杂度:O(n2)
int x;
int length=0;
while(){
scanf("%d",&x);
ListInsert(L,length+1;e);//插到尾部
length++;
}
bool ListInsert(LinkList &L,int i,ELemType e){//在i位置插入元素e
if(i<1)
return false;
LNode *p;
int j=0;
p=L;
while(p!=NUll&&j<i-1){//找到第i-1个结点
p=p->next;
j++;
}
if(p==NULL)//i值不合法,超过链表长度
return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
课本上的头插法:
LinkList List_Taillnsert(LinkList &L){
L=(LNode *)malloc(sizeof(LNode));//初始化单链表
LNode *s,*r=L;
int x;
scanf("%d",&x);
while(x!=9999){//后插操作
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;//永远保持r指向最后一个结点
scanf("%d",&x);
}
r->next=NULL;
return L;
}
头插法建立单链表
- 初始化单链表
int x;
while(){
scanf("%d",&x);
InsertNextNode(L,e);//每次对头结点后插入e
}
后插操作:在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->next=p->next;
p->next=s;
return true;
}
课本头插法
LinkList List_HeadInsert(LinkList &L){
L=(LNode *)malloc(sizeof(LNode));
L->next=NULL;//初始为空链表
int x;
scanf("%d",&x);
LNode *s;
while(x!=9999){
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
重要应用:单链表的逆置
双链表
双链表定义
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;//前驱、后继指针
}DNode,*DLinkList;
双链表的初始化
带头结点
bool InitDlinkLis(DLinkList &L){
L=(DNode *)malloc(sizeof(DNode));//分配一个头结点
if(L==NULL)
return false;
L->prior=NULL;
L->next=NULL;
return true;
}
void main(){
DLinkList L;
InitDLinkList(L);
}
判断双链表是否为空
bool Empty(DLinkList L){//判断双链表是否为空表
if(L->next==NULL)
return true;//为空
else
return false;
}
双链表的插入
后插操作
bool InsertNextDNode(DNode *p,DNode *s){//在p结点后插入S结点
if(p==NULL||s==NULL)//非法参数
return false;
s->next=p->next;
if(p->next!=NULL)//如果p有后继结点
p->next->prior=s;
s->prior=p;
p->next=s;
}
双链表的删除
bool DeleteNextDNode(DNode *p){//删除p的后继结点q
if(p==NULL)//非法参数
return false;
DNode *q=p->next;
if(q==NULL)//p没有后继结点
return false;
p->next=q->next;
if(q->next!=NULL)//q不是最后一个结点
q->next->prior=p;
free(q);
return true;
}
销毁双链表
bool DestroyList(DLinkList &L){
while(L->next!=NULL){
DeleteDNode(L);//删除头结点的下一个结点
}
free(L);//释放头结点
L=NULL;//头指针指向NULL
}
循环链表
循环单链表
初始化一个循环单链表
bool InitList(LinkList &L){
L=(LNode*)malloc(sizeof(LNode));
if(L==NULL)//内存不足,分配失败
return false;
L->next=L;//头结点的next指向头结点
}
判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode *p){
if(p->next==L)//不同之处
return true;
else
return false;
}
循环单链表的巧妙应用
若需要对链表的头部和尾部进行多次操作
可考虑将头指针指向链表尾部
插入删除时可能需要移动L
循环双链表
初始化一个循环双链表
bool InitDLinkList(DLinkList &L){
L=(DNode *)malloc(sizeof(DNode));
if(L==NULL)//内存不足,分配失败
return false;
L->next=L;
L->prior=L;
return true;
}
判断循环双链表是否为空
bool Empty(DLinkList L){
if(L->next==L)
return true;//为空
else
return false;
}
判断结点p是否为循环双链表的表尾结点
bool isTail(DLinkList L,DNode *p){
if(p->next==L)
return true;
else
return false;
}
双链表的插入
bool InitDNextDNode(DNode *p,DNode *s){//在p结点后插入s结点
s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;
}
双链表的删除
bool DeleteNextDNode(DNode *p){//删除p结点的下一结点q
p->next=q->next;
q->next->prior=p;
free(q);
}