模块一:线性结构
- 连续存储【数组】
- 离散存储【列表】
- 线性结构的两种常见应用之一 ——————栈
- 线性结构的两种常见应用之二——————队列
- 专题————递归
模块二:非线性结构
- 树
- 图
模块三:查找和排序
- 折半查找
- 排序
- java中容器与数据结构关系
数据结构的概述
参考书:严蔚敏,吴伟民(伪算法) ----- 高一凡(实现所有伪算法)
定义:
如何把现实中大量而复杂问题以特定的数据类型和特定的存储结构保存到主存储器(内存)中,以及在此基础上为实现某个功能(如查找某个元素,删除某个元素,对所有元素进行排序)而执行的相应的操作,这个操作也叫做算法。
建立概念:个体如何存储,个体间的关系如何存储
数据结构 = 个体 + 个体的关系
算法 = 对存储数据的操作 (算法依赖于特定地存储结构)
算法
解题的方法与步骤
衡量算法的标准
1. 时间复杂度 : 大概程序执行的次数(运行次数最多的步骤到底执行了几次),而非执行的时间(不同机器速度不同)
2.空间复杂度: 算法执行过程中,大概所占用的最大的内存
3.难易程度
4.健壮性
数据结构的地位
数据结构是软件中最核心的课程
程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言
预备知识
指针
地址是内存单元的编号(从0开始的非负整数)
指针就是地址,指针变量就是存放内存地址的变量
指针的本质是一个操作受限的非负整数
- 通过指针在被调函数中修改主调函数的值
- 指针与数组关系
1.数组名存放数组第一个元素的地址/ a == &a[0]) a[3] == *(a+3)
--------汇编解释(段地址+偏移地址)
2.通过被调函数修改数组之值 两个参数---数组名+数组长度
结构体
1. 为什么要讲结构体?
( 类比java中的类)
为了表示一些复杂的数据,单一的变量类型不能满足需求,而出现的一种数据类型。
2. 如何使用结构体:
方法一:
//定义数据类型 != 定义了变量
struct Student{
int sid;
char name[200];
int age;
};
int main()
{
//定义结构体变量
struct Student st = {1000, "xiaoliu", 20};
//printf("%d %s %d\n", st.age, st.name, st.sid);
st.age = 19;
//字符串不能直接赋值
strcpy(st.name, "xiaoliu");
st.sid = 10;
printf("%d %s %d\n", st.age, st.name, st.sid);
return 0;
}
方法二:
int main()
{
struct Student st = {1000, "xiaoliu", 20};
struct Student *p;
p = &st;
p->age = 19; //等价于 (*p).age = 19 等价于 st.age
}
注意:结构体变量不能加减乘除,但能相互赋值。
结构体指针:
struct Student{
int sid;
char name[200];
int age;
};
int main()
{
struct Student st;
f(&st);
g2(&st);
}
//结构体变量赋值函数
void f(struct Student *pst){
(*pst).sid = 11;
strcpy((*pst).name, "xiaoliu");
pst->age =100;
}
//这种方式(非指针传送方式)很费内存,费空间
void g(struct Student st){
printf("%d %s %d\n", st.age, st.name, st.sid);
}
//指针传送方式,不费内存
void g2(struct Student *p){
printf("%d %s %d\n", p->age, p->name, p->sid);
}
动态内存的分配和释放
int main(){
int len;
printf("请输入您需要分配的数组长度\n");
scanf("%d", &len);
//强制类型转换,因为malloc函数只返回第一个字节的地址,需要使系统知道是什么类型的变量
int * pArr = (int *)malloc(sizeof(int)*len);
for(int i=0; i<len; i++){
scanf("%d", &pArr[i]);
}
for(int i=0; i<len; i++){
printf("%d", pArr[i]);
}
//把pArr所代表的动态分配的空间释放(还给操作系统)
free(pArr);
}
问题: 函数调用结束后,函数内开辟的空间还有没有?(跨函数使用内存问题)
1. 局部变量: 空间被释放
2. 使用malloc函数分配的内存,函数调用完成,内存不会自己释放
类的实现: A a = new A();
A a = (A*) malloc(sizeof(A));
跨函数使用内存例子:
struct Student{
int sid;
int name;
};
struct Student * CreateStudent(void){
struct Student *p = (struct Student *)malloc(sizeof(struct Student));
p->name = 1;
p->sid = 2;
return p;
}
int main(){
struct Student *ps;
ps = CreateStudent();
printf("%d %d\n", ps->name, ps->sid);
return 0;
}
模块一: 线性结构【把所有结点用一根直线穿起来】
-------- linkedList / ArrayList
连续存储【数组】
1. 什么是数组
元素相同,大小相等
2. 数组的优缺点
3. 数组的操作
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//定义了一个名为Arr的数据类型
struct Arr{
int *pBase; //存储的是数组第一个元素的地址
int len; //存储的是数组的长度
int cnt; //存储的是数组中当前有效元素的个数
// int increment; //自动增长因子 ---按自动增长因子增加空间
};
void init_arr(struct Arr* pArr, int length);
bool append_arr(struct Arr* pArr, int val);
bool insert_arr();
bool delete_arr(struct Arr* pArr, int pos, int *pVal);
int get();
bool is_empty(struct Arr* pArr);
bool is_full(struct Arr* pArr);
void sort_arr();
void show_arr(struct Arr* pArr);
bool inversion_arr(struct Arr* pArr);
//底层开发 ----实现ArrayList
int main()
{
struct Arr arr;
init_arr(&arr, 6);
append_arr(&arr, 4);
append_arr(&arr, 5);
append_arr(&arr, 3);
show_arr(&arr);
sort_arr(&arr);
show_arr(&arr);
inversion_arr(&arr);
show_arr(&arr);
return 0;
}
//初始化数组
void init_arr(struct Arr *pArr, int length){
pArr->pBase = (int *)malloc(sizeof(int)* length);
//检测内存是否分配好
if(NULL == pArr->pBase){
printf("内存分配失败!\n");
exit(-1); //终止整个程序
}else{
pArr->len = length;
pArr->cnt = 0;
}
}
//展示数组中各元素
void show_arr(struct Arr* pArr){
if(is_empty(pArr)){
printf("数组为空\n");
} else{
for(int i=0; i<pArr->cnt; ++i)
printf("%d ", pArr->pBase[i]);
printf("\n");
}
}
//判断数组是否为空
bool is_empty(struct Arr *pArr){
if(0 == pArr->cnt){
return true;
}else{
return false;
}
}
//判断数组是否满
bool is_full(struct Arr *pArr){
if(pArr->len == pArr->cnt){
return true;
}else{
return false;
}
}
//追加方法
bool append_arr(struct Arr* pArr, int val){
if(is_full(pArr))
return false;
else{
pArr->pBase[pArr->cnt] = val;
pArr->cnt++;
}
}
//插入方法---将val插入pos位置后
bool insert(struct Arr* pArr, int pos, int val){
int i;
if(is_full(pArr))
return false;
if(pos<1 || pos>pArr->cnt+1){
return false;
}
for(i=pArr->cnt-1; i>=pos-1; --i){
pArr->pBase[i+1] = pArr->pBase[i];
}
pArr->pBase[pos-1] = val;
pArr->cnt++;
return true;
}
//删除位置pos处的元素
bool delete_arr(struct Arr* pArr, int pos, int *pVal){
if( is_empty(pArr))
return false;
if(pos<1 || pos>pArr->cnt+1){
return false;
}
int i = pos;
*pVal = pArr->pBase[i-1];
for(i = pos; i<pArr->cnt; i++){
pArr->pBase[i-1] = pArr->pBase[i];
}
pArr->cnt--;
}
//倒置数组
bool inversion_arr(struct Arr* pArr){
int i = 0;
int j = pArr->cnt-1;
int t;
while(i<j){
t = pArr->pBase[i];
pArr->pBase[i] = pArr->pBase[j];
pArr->pBase[j] = t;
i++;
j--;
}
}
//排序
void sort_arr(struct Arr *pArr){
int i;
int j;
int t;
for(i=0; i<pArr->cnt; i++)
for(j=i; j<pArr->cnt; j++){
if(pArr->pBase[i]>pArr->pBase[j]){
t = pArr->pBase[i];
pArr->pBase[i] = pArr->pBase[j];
pArr->pBase[j] = t;
}
}
}
typedef用法:
#include <stdio.h>
#include <stdlib.h>
//typedef用法:
//typedef int ZHANGSAN ----- 为已有的数据类型取个新的名字
//好处: 可省略struct, 写起来更方便
/*
//为struct Student 数据类型取一个新的名字--ST
typedef struct Student {
char name[200];
int age;
}ST;
//PST 表示 struct Student *类型
typedef struct Student {
char name[200];
int age;
}* PST;
*/
//PST 等价于 struct Student *, ST等价于struct Student类型
typedef struct Student {
char name[200];
int age;
}* PST, ST;
int main()
{
/*
struct Student st; // 等价于 ST st;
struct Student *st2; //等价于 PST st2;
*/
ST st = {"lili", 12};
printf("%d", st.age);
PST st2 = &st;
printf("%s", st2->name);
return 0;
}
离散存储【链表】
确定一个链表需要几个参数?
一个参数----头指针,因为通过头指针可以推出链表的其他所有信息
如果希望用一个函数对链表进行处理---只需要一个参数(头指针)
链表的分类:
单链表
双链表:每一个链表有两个指针域
循环链表:能通过任何一节点找到其他节点
非循环链表:
算法:
遍历,清空,销毁,求长度,排序,删除
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//先把链表每个结点的数据类型定义出来
typedef struct Node{
int data;
struct Node* pNext;
}NODE, *PNODE;
//创建链表
NODE* create_list(void){
int len;
printf("请输入您要生成链表节点的个数:len=");
scanf("%d", &len);
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if(pHead == NULL){
printf("分配失败\n");
exit(-1);
}
PNODE pTail = pHead;
pTail->pNext = NULL;
for(int i=0; i<len; i++){
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if(pNew == NULL){
printf("分配失败\n");
exit(-1);
}
printf("请输入第 %d 个结点的值", i+1);
scanf("%d", &(pNew->data));
pTail->pNext = pNew;
pNew->pNext = NULL;
pTail = pNew;
}
return pHead;
}
//遍历链表
void traverse_list(PNODE pHead){
PNODE node = pHead->pNext;
while(node != NULL){
printf("%d\n", node->data);
node = node->pNext;
}
}
//判断链表是否为空
bool is_empty(PNODE pHead){
if(pHead->pNext == NULL){
return true;
}
return false;
}
//链表长度
int length_list(PNODE pHead){
PNODE node = pHead->pNext;
if(node == NULL){
return 0;
}
int i=1;
while(node->pNext != NULL){
i++;
node = node->pNext;
}
return i;
}
//排序算法
void sort_link(PNODE pHead){
int len = length_list(pHead);
int i, j, t;
PNODE p, q;
for(i=0, p=pHead->pNext; i<len-1; i++, p=p->pNext){
for(j=i, q=p->pNext; j<len-1; j++, q=q->pNext){
if(p->data > q->data){
t = p->data;
p->data = q->data;
q->data = t;
}
}
}
}
//在第pos个节点的前面插入一个节点
bool insert_Node(PNODE pHead, int pos, int val){
int i = 0;
PNODE p = pHead;
while(NULL != p && i<pos-1){
p = p->pNext;
++i;
}
if(i> pos-1 || NULL == p){
return false;
}
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if(pNew == NULL){
printf("分配失败\n");
exit(-1);
}
pNew->data = val;
PNODE q = p->pNext;
p->pNext = pNew;
pNew->pNext = q;
}
//删除pos后的元素
bool delete_link(PNODE pHead , int pos){
int i = 0;
PNODE p = pHead;
while(NULL != p && i<pos-1){
p = p->pNext;
++i;
}
if(i> pos-1 || NULL == p){
return false;
}
PNODE q = p->pNext;
p->pNext = p->pNext->pNext;
free(q);
q == NULL;
}
测试
int main()
{
PNODE pHead = NULL;
pHead = create_list(); //创建一个非循环单链表,将链表头结点的地址赋给pHead
traverse_list(pHead);
if(is_empty(pHead)){
printf("链表为空\n");
}
printf("链表长度为 %d", length_list(pHead));
sort_link(pHead);
traverse_list(pHead);
insert_Node(pHead, 2 , 100);
traverse_list(pHead);
if(!delete_link(pHead, 2)){
printf("删除失败,不存在\n");
}
traverse_list(pHead);
return 0;
}
栈
定义:栈是限定只能在表位进行插入和删除操作的线性表
后进先出的线性表
分类:
顺序栈:用一段连续的存储空间依次存放自栈底到栈顶的数据元素
链栈