1. 什么是数据结构?
数据结构:数据的逻辑结构、存储结构及操作
数据
逻辑结构
存储结构
操作
#数据相关
数据 数据元素 数据项 节点
(1)数据: 信息的载体,计算机的研究对象不再单纯是数值,研究的是数据间的关系及操作(运算)
(2)数据元素:数据元素是数据的基本单位,由若干个基本项组成(学号、姓名、班级、学院)
struct student
{
char name[20]; //数据项
int age;
float score;
}s1;
s1 //数据元素
struct student s[100];
基本项 //称为数据项
(3)节点: 数据元素就叫节点
///
1.2 逻辑结构(构思阶段,找规律)
(1)什么是逻辑结构?
数据与数据之间的联系和规律
(2)数据关系有哪些?
- 逻辑关系 特点 逻辑结构 应用
- 线性关系 一对一 线性结构 线性表(顺序表,链表(单向链表,单向循环链表,双向链表,双向循环链表),栈,队列)
- 层次关系 一对多 树状结构 树
- 网状关系 多对多 图状结构 图
1.3 存储结构(不同的存储结构,就是不同解题方法)
(1)什么是存储结构
概念:逻辑结构在计算机上的具体实现
(2)存储方法
1)顺序存储(数组、表)
顺组存储结构:在内存上是连续存储的
struct student s[100];
数据元素在按照一定的逻辑顺序存储于一段连续的存储空间上
连续的存储空间
2)链式存储
数据元素存储在存储器上的不同位置,通过地址(链指针)建立他们之间的联系
struct node_t
{
struct student data; //数据域
struct node_t *next; //指针域,指向下一个节点的指针
};
3)索引存储
索引存储 = 索引表 + 数据文件
举例:电话本
索引表 数据文件
姓氏 + 地址 姓名 + 电话号码
提升查找速度
4)散列存储
数据元素的存储与关键码之间存在对应关系的查找技术
举例:存储年龄和人口数之间的对应关系
1.4 操作(运算)
对数据的操作: 增 删 改 查
图书管理系统
买了一本书
丢了一本书
一本书的信息录入错误了
别人来借书
1.5 数据结构总结
逻辑结构(前驱和后继):
逻辑关系 特点 逻辑结构 应用
线性关系 一对一 线性结构 线性表(顺序表,链表(单向链表,单向循环链表,双向链表,双向循环链表),栈,队列)
层次关系 一对多 树状结构 树
网状关系 多对多 图状结构 图
存储结构:
顺序存储结构:在内存当中是连续存储的
链式存储结构
索引存储结构
散列存储结构
- 算法
1.1 如何评价一个算法的好坏?
(1)消耗时间的多少 消耗时间少好
(2)消耗内存的大小 消耗内存小
(3)可读性 可维护 移植性
- 线性表
特征:一对一,每个节点最多有一个前驱和一个后继,首节点无前驱,尾节点无后继
线性表:顺序表 链表 栈 队列
3.1 顺序表(数组,在内存当中连续存储,数据元素个数固定)
逻辑结构: 线性结构
存储结构: 顺序存储结构
操作(运算): 增 删 改 查
####练习#####
定义一个全局变量 int last = 5; //last的值,时刻代表当前数组中有效元素的个数,last是几,有效元素就是几
int a[100] = {10,20,30,40,50};//部分初始化,当前的5个数,被称为有效元素个数
//顺序表的插入和删除操作
int a[100] = {11,22,33,44,55};
showArray(a);
//增加,在第3个位置,插入一个数1000
insertIntoA(a, 3, 1000);
showArray(a);//11 22 1000 33 44 55
deleteFromA(a, 3);//删除第3个位置的数据
showArray(a);//11 22 33 44 55
#include <stdio.h>
int last = 5;//全局变量,last的值时刻代表数组中有效元素的个数
//将数组中的有效元素打印输出
void showArray(int* p)
{
int i;
for(i = 0; i < last; i++)
{
printf(“%d “,p[i]);
}
printf(”\n”);
}
//int post 插入的位置 第几个
//int x 插入的数据
void insertIntoA(int* p, int post, int x)
{
//1.从插入位置开始整体向后移动一个位置
//2.将新的数据插入
//3.有效元素的个数+1 last++
}
//删除指定位置的数据
void deleteFromA(int* p, int post)
{
//1.整体向前移动一个位置
//2.有效元素个数 -1 last–
}
int main(int argc, const char *argv[])
{
int a[100] = {11,22,33,44,55};
showArray(a);
//增加,在第3个位置,插入一个数1000
insertIntoA(a, 3, 1000);
showArray(a);//11 22 1000 33 44 55
deleteFromA(a, 3);//删除第3个位置的数据
showArray(a);//11 22 33 44 55
return 0;
}
#include <stdio.h>
int last = 5;//全局变量,last的值时刻代表数组中有效元素的个数
//将数组中的有效元素打印输出
void showArray(int* p)
{
int i;
for(i = 0; i < last; i++)
{
printf(“%d “,p[i]);
}
printf(”\n”);
}
//int post 插入的位置 第几个
//int x 插入的数据
void insertIntoA(int* p, int post, int x)
{
//1.从有效元素位置下标--插入位置的下标整体向后移动一个位置 last-1 – post-1
int i;
for(i = last-1; i >= post-1; i–)
p[i+1] = p[i];
//2.将新的数据插入
p[post-1] = x;//post-1 因为第post个位置,对应的下标是post-1
//3.有效元素的个数+1 last++
last++;
}
//删除指定位置的数据
void deleteFromA(int* p, int post)
{
//1.将从删除位置的后一个位置的下标–最后一个有效元素,整体向前移动一个位置
//post – last-1
int i;
for(i = post; i <= last-1; i++)
p[i-1] = p[i];
//2.有效元素个数-1
last–;
}
int main(int argc, const char *argv[])
{
int a[100] = {11,22,33,44,55};
showArray(a);
//增加,在第3个位置,插入一个数1000
insertIntoA(a, 6, 1000);
showArray(a);//11 22 1000 33 44 55
deleteFromA(a, 6);//删除第3个位置的数据
showArray(a);//11 22 33 44 55
return 0;
}
//
3.2 typedef 关键字
作用:类型重定义,增强程序可移植性
typedef int size_t;
size_t a = 10; //等价于int a = 10
void *malloc(size_t size);//size_t 类型重定义
struct student
{
char name[20];//成员变量
int age;
int score;
};//一定要有分号
//
学生结构体
struct student
{
char name[20];
int age;
int score;
};//一定要有分号
(1)方法一:
typedef struct student stu_t;
stu_t s; //等价于struct student s;
(2)方法二:
//在定义结构体的时候,再起个名
typedef struct student
{
char name[20];
int age;
int score;
}stu_t;//一定要有分号
stu_t s; //等价于struct student s;
(3)方法三:
//匿名结构体重命名
typedef struct
{
char name[20];
int age;
int score;
}stu_t;
stu_t s;
#include <stdio.h>
//方法三 匿名结构体类型重定义
/*
typedef struct
{
char name[20];
int age;
}stu_t;
*/
//方法二,在定义结构体的同时,进行类型重定义
typedef struct student
{
char name[20];
int age;
}stu_t;
typedef int ZZZ;
#if 0
//方法一
//typedef struct student stu_t;
#endif
int main(int argc, const char *argv[])
{
// ZZZ a = 10;//等价于int a = 10;
// printf(“a is %d\n”,a);
stu_t s = {“asan”,19}; //等价于struct student s = {“asan”,19};
printf(“%s %d\n”,s.name,s.age);
return 0;
}
///
3.3 顺序表的操作
创建 //create
空 //empty
满 //full
顺序 //sequence
表 //list
int last = 5;//有效元素个数是5
int a[100] = {10,20,30,40,50};
#define N 100
typedef struct
{
int data[N]; //用来存储有效数据的
int last; //有效元素的个数
}seqlist_t;
(1)创建空顺序表 craeteEmptySeqlist()
(2)向指定位置插入元素 insertIntoSeqlist()
(3)判断顺序表是否为满 满返回值是1 未满返回值是0 isFullSeqlist()
(4)删除指定位置元素 deleteFromSeqlist()
(5)判断顺序表是否为空 空返回值是1,未空返回值是0 isEmptySeqlist()
(6)求表长 getLengthSeqlist()
(7)清空顺序表 clearSeqlist()
(8)查找指定元素的位置 searchDataSeqlist()
(9)遍历顺序表 showSeqlist()
///结构体和 malloc复习代码
#include <stdio.h>
#include <stdlib.h>
//int last = 5;//全局
//int a[100] = {11,22,33,44,55};//数组,用来存储有效数据
typedef struct
{
int last;//last时刻代表有效元素的个数
int a[100];//用来存储有效数据
}seqlist_t;
//sequence 顺序
//list 表
//将数组中的有效元素全部打印输出
void showSeqlist(seqlist_t* p)//实参初始化形参 seqlist_t* p = p(main函数中的p)
{
int i;
for(i = 0; i < p->last; i++)
{
printf(“%d “,p->a[i]);
}
printf(”\n”);
}
int main(int argc, const char argv[])
{
//seqlist_t s = {5, {11,22,33,44,55}}; //栈区
seqlist_t p = (seqlist_t*)malloc(sizeof(seqlist_t));//堆区
if(p == NULL)
{
printf(“malloc failed!!\n”);
return -1;//提前结束程序
}
p->last = 5;
p->a[0] = 11;
p->a[1] = 22;
p->a[2] = 33;
p->a[3] = 44;
p->a[4] = 55;
showSeqlist§;
free§;
return 0;
}
///顺序表操作///
#include <stdio.h>
#include <stdlib.h>
//int last = 5;//全局
//int a[100] = {11,22,33,44,55};//数组,用来存储有效数据
#define N 100
typedef struct
{
int last;//last时刻代表有效元素的个数
int data[N];//用来存储有效数据
}seqlist_t;
//sequence 顺序
//list 表
//1.遍历顺序表,将所有的有效元素打印输出
void showSeqlist(seqlist_t* p)//实参初始化形参 seqlist_t* p = p(main函数中的p)
{
int i;
for(i = 0; i < p->last; i++)
{
printf(“%d “,p->data[i]);
}
printf(”\n”);
}
//2.创建一个空的顺序表
seqlist_t* createEmptySeqlist()
{
seqlist_t* p = (seqlist_t*)malloc(sizeof(seqlist_t));//堆区
if(p == NULL)
{
printf(“malloc failed!!\n”);
return NULL;//提前结束函数
}
p->last = 0;//因为创建的是空的顺序表,所以last的值初始化为0,有效元素个数为0
return p;
}
//3. 向指定的位置插入数据
int insertIntoSeqlist(seqlist_t* p, int post, int x) //seqlist_t* q = p;
{
//0.容错判断,对插入位置是否合理,进行条件判断
if(post < 1 || post > p->last+1 || isFullSeqlist§)
{
printf(“insertIntoSeqlist failed!!\n”);
return -1;//通常用-1来表达失败
}
//1.将最有一个有效元素的下标到插入位置的下标整体向后一个位置
int i;
for(i = p->last-1; i >= post-1; i–)
p->data[i+1] = p->data[i];
//2.插入数据
p->data[post-1] = x;
//3.有效元素个数+1
p->last++;
return 0;//用0来表达成功
}
//4.判断顺序表是否为满 满返回值是1,未满是0
int isFullSeqlist(seqlist_t* p)
{
/*
//方法一:有效元素个数 == 数组的长度,表满
if(p->last == N)
return 1;
else
return 0;
//方法二:
return p->last == N ? 1 : 0;
/
//方法三
return p->last == N;//p->last == N 表达式为真,C语言中,真用1来表达, p->last == N表达式为假,C语言中假用0来表达
}
//5. 判断是否为空 空返回值是1,未空返回值0
int isEmptySeqlist(seqlist_t p)
{
return p->last == 0 ? 1 : 0;
}
//6.删除指定位置的数据
int deleteFromSeqlist(seqlist_t* p, int post)
{
int i;
//0.容错判断 对删除位置是否合理,进行判断
if(post < 1 || post > p->last || isEmptySeqlist§)
{
printf(“deleteFromSeqlist failed!!\n”);
return -1;
}
//1.将删除位置的后一个位置的下标到最后一个有效元素下标,整体向前移动一个位置,覆盖删除
for(i = post; i <= p->last-1; i++)
{
p->data[i-1] = p->data[i];
}
//2.有效元素个数-1
p->last–;
return 0;
}
//7.求顺序表的长度,长度就是有效元素的个数
int getLengthSeqlist(seqlist_t* p)
{
return p->last;
}
//8.清空顺序表
void clearSeqlist(seqlist_t* p)
{
p->last = 0;
}
//9.查找指定数据出现的位置,返回数组中的位置下标
//int x,代表被查找的数据
int searchDataSeqlist(seqlist_t* p, int x)
{
int i;
for(i = 0; i < p->last; i++)
{
if(p->data[i] == x)
return i;
}
return -1;//代表没有找到
}
int main(int argc, const char argv[])
{
seqlist_t p = createEmptySeqlist();//得到malloc申请结构体变量空间大小的首地址
insertIntoSeqlist(p, 1, 11);//将首地址告诉insertIntoSeqlist函数
insertIntoSeqlist(p, 2, 22);
insertIntoSeqlist(p, 3, 33);
insertIntoSeqlist(p, 4, 44);
insertIntoSeqlist(p, 5, 55);
insertIntoSeqlist(p, 3, 1000);
showSeqlist§;//将首地址告诉showSeqlist函数
deleteFromSeqlist(p,3);
showSeqlist§;
printf(“len is %d\n”,getLengthSeqlist§);
printf(“33的位置下标是%d\n”,searchDataSeqlist(p,33));
clearSeqlist§;
printf(“len is %d\n”,getLengthSeqlist§);
free§;
return 0;
}
//2.链表
逻辑结构: 线性结构
存储结构: 链式存储结构
链式存储结构: 内存当中是不连续存储的,通过指针将每个节点连接在一起
//node 节点的意思
//link 链接
//定义一个节点
typedef struct node_t //注意此处的node_t不能省略不写
{
int data;//数据域,用来保存有效数据的
struct node_t* next;//指针域 用来指向下一个节点的指针
}link_node_t;
sizeof(struct node_t) --> 8
link_node_t s;
单向链表可以分为 有头的单向链表 和 无头的单向链表
无头单向链表:链表中的每一个节点的数据域,都是有效的数据
有头单向链表:链表中的第一个节点的数据域是无效的,其他是有效的
###有头单向链表和无头的单向链表只是一个相对的概念
2.1 无头的单向链表
#include <stdio.h>
typedef struct node_t
{
int data;//数据域
struct node_t* next;//指针域
}link_node_t;
int main(int argc, const char argv[])
{
//无头单向链表,链表中的每个节点的数据域,都是有效的数据
//1.定义5个节点
link_node_t a = {10,NULL};
link_node_t b = {20,NULL};
link_node_t c = {30,NULL};
link_node_t d = {40,NULL};
link_node_t e = {50,NULL};
//2.将5个节点连接在一起
a.next = &b;
b.next = &c;
c.next = &d;
d.next = &e;
//3.定义一个头指针,保存链表第一个节点的地址
link_node_t p = &a;
//4.遍历无头单向链表
while(p != NULL)
{
printf(“%d “,p->data);
p = p->next;
}
printf(”\n”);
return 0;
}
2.2 有头的单向链表
#include <stdio.h>
typedef struct node_t
{
int data;//数据域
struct node_t* next;//指针域
}link_node_t;
int main(int argc, const char argv[])
{
//有头单向链表,链表中的每个节点的数据域,都是有效的数据
//1.定义5个节点
link_node_t a = {10,NULL};
link_node_t b = {20,NULL};
link_node_t c = {30,NULL};
link_node_t d = {40,NULL};
link_node_t e = {50,NULL};
//2.将5个节点连接在一起
a.next = &b;
b.next = &c;
c.next = &d;
d.next = &e;
//3.定义一个头指针,保存链表第一个节点的地址
link_node_t p = &a;
//4.遍历无头单向链表
while(p != NULL)
{
printf(“%d “,p->data);
p = p->next;
}
printf(”\n”);
p = &a;//让头指针,再重新指向链表的第一个头节点,因为遍历一遍无头后,p是空指针
//5.遍历有头单向链表
while(p->next != NULL)
{
p = p->next;//向移动
printf(“%d “,p->data);//后打印
}
printf(”\n”);
return 0;
}
#作业1:
写一个有头单向链表,一直输入学生成绩,存入链表中,直到输入-1 结束程序
每输入一个学生成绩,就malloc申请一个新的节点,将输入的成绩保存到数据域,并将该新节点链接到链表的尾巴
//要求:每一个链表的节点由动态内存分配malloc得到,包括头节点
// 编程思想:
//1.malloc创建一个链表的头节点,数据域不用装东西
//2.
//可以再定义一个指针变量,永远指向当前链表的尾巴
int score;
while(1)
{
scanf(“%d”,score);
if(score == -1)
break;
//3.申请节点大小的空间,将 score 保存到节点的 数据域score中,将这个节点连接链表的尾巴
}
//3.循环遍历打印链表
#include <stdio.h>
#include <stdlib.h>
typedef struct node_t
{
int data;
struct node_t* next;
}link_node_t;
int main(int argc, const char argv[])
{
int score;//用来保存输入的学生成绩
//1.创建一个头节点,作为链表的头
link_node_t ptail = NULL;//ptail尾指针,永远指向当前链表的尾巴
link_node_t* phead = malloc(sizeof(link_node_t));
if(phead == NULL)
{
printf(“phead malloc failed!!\n”);
return -1;
}
phead->next = NULL;//有可能输入第一个学生成绩就是-1,循环结束了,是一个空的链表,作为遍历有头链表结束条件
//由于最开始只有一个节点,既是头节点,也是尾节点
ptail = phead;
//2.循环输入学生成绩,输入一个成绩,就保存一个新创建节点的数据域中,插入在链表的尾巴上
while(1)
{
printf(“请输入学生成绩:\n”);
scanf(“%d”,&score);
if(score == -1)
break;
//创建新的节点,用来保存输入的成绩
link_node_t* pnew = malloc(sizeof(link_node_t)); //pnew永远指向新创建的节点
if(pnew == NULL)
{
printf(“pnew malloc failed!!\n”);
return -1;
}
//申请空间后,立刻赋值,将成绩存入到新创建的节点中
pnew->data = score;//输入的成绩,保存到新节点的数据域
pnew->next = NULL;
//将新的节点,插入在链表的尾巴上
//尾插法思想:尾指针,永远指向当前链表的尾巴,也就是最后一个节点,只要让最后一个节点的next指针,指向新的节点,就算插入成功
ptail->next = pnew;//插入在尾巴上
//将尾指针移动当前链表的尾巴,因为插入之后,链表变长了
ptail = pnew;//ptail = ptail->next;
}
//遍历有头单向链表
while(phead->next != NULL)
{
phead = phead->next;
printf(“%d “,phead->data);
}
printf(”\n”);
return 0;
}