课时 1、P1_什么叫做数据结构
全教程大纲:
1、数据结构的概述
推荐用书:
严蔚敏(考研选择)《数据结构》 吴伟民的《数据结构》 高一凡《数据结构》(有具体程序)
伪算法:即只是解题的思路,并不能在程序中运行得出结果;
高一凡的书中将严蔚敏的书的伪算法全部实现(可以运行)(只有c和c++版)

数据结构的定义:
我们如何把现实中大量而复杂的问题以特定的数据类型和特定的存储结构保存到主存储器 (内存)中,以及在此基础上为实现某个功能(例如查找元素、删除元素、排序等等)而执行的操作,这个相应的操作也叫做算法。
数据结构 = 个体 + 个体关系
算法 = 对存储数据的操作 (狭义的算法)
数据结构:研究在内存中数据以什么如何保存?每个数据之间的存储关系的什么?
数据结构解决数据存储的问题,算法解决进行操作的问题;
存储的方式不同,算法就不同;(即:不同的存储方式,操作它的方式不同,例如以链表的方式存储数据和以数组的方式存储数据,在取出数据进行操作时的方法是不一样的);
补充知识(数组、链表、树、图):
数组:当我们存储的数据较少时,我们可以采取用数组的方式进行存储,但是数组需要的是一段连续的内存空间,所以不适合存放大量的数据,且中间没有空数据;
链表:链表可以用来存放大量的数据,它是利用碎片化的空间来进行存储,每一个节点都存放了下一个节点的地址;
树:当我们的数据存在层级关系时(例如一个公司的人事系统),有一定的结构关系时,采用树形结构是比较合适的;
图:当节点于节点的关系没有固定的关系,每一个节点都可能存在关系时,采用图(例如公交系统,不同的车的路线会有交集);

2、预备知识
3、模块一:线性结构
连续存储(数组)
离散存储(链表)
线性结构的两种常见应用之一 ------ 栈
线性结构的两种常见应用之二 ------ 队列
专题:递归
1、1+2+3+4+。。。。。。+100的和
2、求阶乘
3、汉诺塔
4、走迷宫
模块二:非线性结构


模块三:非线性结构
折半查找
排序:
冒泡排序
插入排序
选择排序
快速排序
归并排序

课时 2、P2_衡量算法的标准
1、算法:解题的方法和步骤
2、衡量算法的标准:
时间复杂度(大概程序要执行的次数,而非执行的时间(因为不同的机器执行速度不同))
空间复杂度(算法执行过程中大概所占的最大内存)
难易程度(简单易懂)
健壮性(程序可靠,是否有漏洞)
课时 3、P3_数据结构的特点
1、数据结构的地位
数据结构是软件中最核心的课程
2、数据库(扩充知识)
字段(表示数据属性)
记录(一个事物)
表(事物的集合)
3、什么是程序?
程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言
课时 4、P4_预备知识_指针_1
1、看懂伪算法比实现代码容易;
2、数据结构的基本知识是链表;
3、数据结构也需要指针;
4、要掌握:
指针
结构体
动态内存的分配和释放
课时 5、P55_预备知识_指针_2
1、指针的重要性:指针是C语言的灵魂
2、地址:内存单元的编号(内存的编号不会重复,但是内存的内容可以重复),地址是从0开始的非负整数,范围:0 — 4G-1
3、cpu可以直接访问内存,不可以直接访问硬盘;
地址总线:确定对哪一块内存进行处理;
数据总线:进行数据传送;
控制总线:控制到底是读出数据还是写入数据
4、指针就是地址,地址就是指针;
5、指针变量的存放内存单元地址(内存单元的编号)的变量;
6、指针的本质:是以一个操作受限的非负整数(不可进行运算操作);
7、分类:
基本类型指针
int * p; //定义一个指针变量,该指针变量只能存放int类型的数据的地址
int i = 10; //
p = &i; //将指针变量p指向i,p内部存放的是i的地址,*p代表的就是i,两者可以替换
//虽然p指向了i,*p代表的就是i,但是改变p的值不影响i,改变i的值也不影响p;
//指针变量也是变量,只是指针变量存放的是变量地址,不是内存单元的内容;
//常量和表达式前不能加 &(取地址符)
//普通变量前不能加 *(只有在指针变量前面才可以加)
指针和数组的关系

8、指针在使用之前必须要初始化,不初始化时内部存放的是一个垃圾值,*p就不可以操作,因为垃圾值地址所指向的内存空间并没有分配给程序,所以程序不可以对垃圾值地址的内存空间的内容进行读操作或者写入操作;
9、当我们定义变量的时候,系统就会给该变量分配内存,程序对分配的内存由控制权;当程序终止之后,内存就会被释放,由系统进行控制;
即:程序在运行时需要想操作系统申请存储空间,系统会给该程序分配内存,程序对系统所分配的内存有使用控制权,当程序执行完毕之后,程序会释放内存,系统回收内存,不再对系统分配的该段内存有控制权,但是即使程序执行完毕释放了内存,内存中的数据也不会被销毁,这些遗留的数据就是垃圾数据;
在运行期间,该程序分配的内存不被其他软件所使用;
操作系统收回的是使用权限,并不清除数据,所以变量需要初始化;
10、
内存的基本划分单位是字节(即每个字节就是内存划分的一个小单元);每一个字节都有唯一的一个编号,这个编号就是地址;
11、要更改局部变量的值必须要使用指针,跨函数操作
#include <stdio.h>
void f(int i)
{
i = 88;
}

void g(int * p)
{
*p = 100;
}

int main(void)
{
int i = 10;

printf("i = %d\n",i);f(i);//f函数和main函数中的都是局部变量,函数执行完毕之后,所占据的内存都被释放,所以两者的i变量只是名字相同的各自函数的局部变量
printf("i = %d\n",i);g(&i);//利用指针指向变量,再用 *指针变量名  来改变指向地址的数据的值
printf("i = %d\n",i);

}
/*
输出结果:
i = 10
i = 10
i = 100
*/

12、
一维数组的名字是一个指针常量,默认指向数组的第一个元素,它存放的是第一个元素的地址,不可以被更改;
数据一定连续,是连续的内存空间;
指针变量指向的是所分配的空间,只不过存放的是所指向空间的第一个字节的地址,所指向的空间可能不止一个字节;
例如:a[3] 等价于 *(a+3) a是数组名,指向数组的第一个元素,a+3代表指向数组的第四个元素(数组的第一个元素的下标为0)
%p代表输出元素地址;
数组的特性:(数组内部元素的内部内存空间连续,且数组名为一个指针常量)
代码:
#include <stdio.h>

int main(void)
{
int array[5] = {1,2,3,4,5};
//array[3] == (a+3)
//array代表的是第一个元素的地址
printf("%p\n",array+2);
printf("%p\n",array+3);
printf("%p\n",array+4);
printf("%d\n",array[3]);
printf("%d\n",
(array+3));
//*a+3 等价于 a[0]+3
printf("%d\n",*array+2);
printf("%d\n",array[0]+2);
}
输出结果:
000000000062FE08
000000000062FE0C
000000000062FE10
4
4
3
3
注意:上诉代码打印出来的数组的地址为什么是不连续的?
上面的数组是int类型的,所以每个元素所占的字节数是四,所以四个字节为一个空间,如果定义为char类型的数组,那么就一个字节一个元素,每个元素的空间都是连号的;

13、
数组的赋值和输出(可以在main函数中用for循环输出,也可以调用函数输出);
利用函数赋值或输出数组,需要传入数组的第一个元素的地址,数组的长度作为参数;
传入参数:数组名 数组长度
代码:
for循环实现:
#include <stdio.h>

int main(void)
{
int array[5];
int i,j;
int val;

for(i=0;i<5;i++)
{printf("请输入需要存入的数字:") ;scanf("%d",&val);array[i] = val;
}printf("\n\n");
printf("数组结果:") ;
for(j=0;j<5;j++)
{printf("%d   ",array[j]);
}

}

函数实现:
#include <stdio.h>
//插入元素函数
void get(int * p,int len)
{//p和array一样都是指向数组的第一个元素,保存都是数组的第一个元素第一个字节的地址,所以可以把p当作 array来使用
// p[1] 等价于 *(p+1) 等价于 *(array+1) 等价于 array[1]
int val,i;
for(i=0;i<len;i++)
{
printf(“请输入需要存入的数字:”) ;
scanf("%d",&val);
p[i] = val;
}
}
//输出元素函数
void push(int * q,int len1)
{
int i;
for(i=0;i<len1;i++)
{
printf("%d ",q[i]);
}
}

int main(void)
{
int array[5];

get(array,5);//输入   数组名字    数组长度 printf("\n\n");
printf("数组结果:");
push(array,5);//输出   数组名字    数组长度

}

课时 6、P6_所有的指针变量只占4个子节 用第一个字节的地址表示整个变量的地址
1、复习上节课内容
double是8个字节,一个字节一个地址,8个字节8个地址,当指针变量指向double类型的一个变量时,指针变量内部存放的是首字节的地址,但是它指向的是8个字节的地址;
2、代码:数组的元素是连续的内存空间存放,但是根据类型不同,每个元素所占大小不同;
#include <stdio.h>

int main(void)
{
double arr[3] = {1.1, 2.2, 3.3};
double * p;
double * q;

p = &arr[0];//将第一个元素的地址赋值给p
q = &arr[1];//将第二个元素的地址赋值给p
//第一个元素和第二个元素相差  8
printf("%p  \n",p);
printf("%p  \n",q);

}
/*
输出结果:
000000000062FDF0
000000000062FDF8
*/
课时 7、P7_如何通过函数修改实参的值
1、函数写在最后,但是前面需要调用函数,则需要在程序前面写前置声明;
前置声明必须要说明函数是否存在参数,和参数类型;
修改其他函数变量的值必须要用指针进行修改,因为指针可以跨函数使用,而普通的变量是局部变量不可以跨函数使用,程序执行完毕之后,变量所占用的内存空间就会被释放;
2、改变指针变量的值或者改变指针变量所指向的内存空间的值,都不会互相影响;
如何改变指针变量的值?(指针变量内部存放的是地址,并且是int *类型)
代码:
#include <stdio.h>

void f(int **);//f函数的前置声明

int main(void)
{
int i = 10;
int * p;
p = &i;//p指向i,p内部存放的是i的地址
printf("%p \n",p);

f(&p);
printf("%p \n",p);

}
void f(int ** q)
{
*q = (int *)0xFFFFFFFF;//修改指针变量p内部存放的地址
//(int )表示强制类型转换,将 其转换为int类型
}
/

输出结果:
000000000062FE1C
00000000FFFFFFFF
*/
注意:
p是指针变量 是int *类型 *p代表的就是指针变量指向的哪个内存空间的数据
q是int **类型 存放的是指针变量的地址
无论我们要利用函数更改什么什么类型的地址,都必须通过传入该数据的地址来利用指针变量跨函数修改数据的值;
课时 8、P8_结构体的使用概述
1、为什么会出现结构体?
为了表示一些复杂的数据,而普通的基本类型变量无法满足要求;
与Java中的类相似,不过类更加全面;
2、什么叫结构体?
用户根据实际需要自己定义的符合数据类型;
3、如何使用结构体?
定义结构体就是定义一个新的数据类型;
1)、普通的定义结构体和定义结构体变量的方式代码:
#include <stdio.h>
#include <string.h>

//定义一个结构体 (数据类型) 结构体的名称为: struct Student
struct Student
{
int num;
char name[100];
int age;
};//分号不可以省略

int main(void)
{
struct Student st1 = {1001, “zhangshan”, 20 };
printf(“num = %d name = %s age = %d\n”,st1.num,st1.name,st1.age);
//%s表示以字符串的类型输出
struct Student st2;
st2.num = 1002;
strcpy(st2.name,“lisi”);
//st2.name = “lisi”;// error需要使用函数赋值 字符串类型
st2.age = 18;
printf(“num = %d name = %s age = %d\n\t”,st2.num,st2.name,st2.age);
}
注意:
\n表示换行符,\t表示制表符;
字符串在C语言中用%s的形式输出;
字符串在C语言中赋值要使用strcpy()函数,并且要加头文件:#include <string.h>
上述代码表现了结构体的常见的两种使用的方式和定义结构体的方式;
要访问结构体成员:结构体变量名.成员名 例如:st.name 表示结构体类型的变量st,调用它的name成员;
2)、我们可以使用指针来使用结构体 代码:
#include <stdio.h>
#include <string.h>

struct Student
{
int num;
char name[100];
int age;
};//分号不能省略
int main(void)
{
//第一种方式
struct Student st1 = {10001 , “shushu” , 18};
printf("%d %s %d\n",st1.num,st1.name,st1.age);

//第二种方式
struct Student st2;
st2.num = 10002;
strcpy(st2.name,"zhangsan");
st2.age = 20;
printf("%d    %s     %d\n",st2.num,st2.name,st2.age);//常用方式----指针
struct Student * pst;  //pst是结构体指针变量
pst = &st2;
pst->num = 10003;
strcpy(pst->name,"lisi");
pst->age = 22;
printf("%d    %s     %d\n",st2.num,st2.name,st2.age);

}
/*
输出结果:
10001 shushu 18
10002 zhangsan 20
10003 lisi 22
/
注意:pst->num 等价于
(pst).num 等价于 st2.num
定义的指针指向了st2,则*(pst)就等价于st2

4、注意事项?
不能加减乘除,但是可以相互赋值;
普通结构体变量和结构体指针变量作为函数传参的问题;
(补充:无论什么变量,在定义的时候系统就已经进行了内存空间的分配,只不过在没有初始化的时候存在变量内存空间内部的是垃圾数据而已,其实内存空间定义时就已经存在)
代码:利用函数实现结构体变量的赋值和输出(需要使用结构体指针变量)
#include <stdio.h>
#include <string.h>

void get(struct Student * Pst);//前置声明
void push1(struct Student st);
void push2(struct Student * pst);
struct Student
{
int num;
char name[100];
int age;
};//分号不能省略
int main(void)
{
struct Student st2;//已经为st2分配好了内存
get(&st2); //赋值函数
push1(st2);//输出函数
push2(&st2);//输出函数
}
void get(struct Student * Pst)
{
//Pst指向结构体变量
(*Pst).num = 10001;
strcpy(Pst->name,“shushu”);
Pst->age = 18;
}
//该输出函数是普通的结构体变量,所以传入的实参的大小是一个结构体变量的大小(大于108个字节)
void push1(struct Student st)
{
printf("%d %s %d\n",st.num,st.name,st.age);
}
//该输出函数的实参和形参都是指针变量,指针变量内部存的是指向内存空间的地址的第一个字节的地址,只占4个字节的大小
void push2(struct Student * pst)
{ //(*pst)代表的是就是结构体变量
printf("%d %s %d\n",(pst).num,pst->name,pst->age);
}
/

输出结果:
10001 shushu 18
10001 shushu 18
*/
课时 9、P9_malloc()动态分配内存概述
1、使用了malloc函数分配的就是动态内存分配;
动态分配的内存可以用free释放,并且程序员可以手动释放,不必等到程序终止;
使用malloc动态分配内存需要写头文件:#include <malloc.h>
举例:动态创建数组 代码:
#include <stdio.h>
#include <string.h>
#include <malloc.h>

int main(void)
{
int len;
int array[5] = {1,2,3,4,5}; //静态内存分配,程序终止则释放,不可以手动释放

printf("请输入你需要的数组的长度:\n");
scanf("%d",&len);

//根据用户输入的地址的长度来请求系统动态分配相应长度的内存空间
//len 表示数组的元素的个数,sizeof表示返回括号内类型数据的长度,一个元素是一个int,len*长度=所有元素的长度
//malloc动态分配内存返回是的所分配的内存的第一个字节的地址,所以我们应该用指针变量存放
//动态分配的内存只是分配了空间给你,如何划分空间由用户决定
//(int *) 表示强制类型转换,将系统分配的空间以int类型(四个字节)的单位划分
int * pArr = (int *)malloc(sizeof(int) * len); //动态分配内存
//pArr指向的是所分配的内存空间的第一个字节的,所保存的第一字节的地址,与一维数组相似4
//一维数组的数组名是一个指针常量,默认指向的是第一个元素的地址,数组名保存的也就是数组空间的第一个字节的地址
//pArr就相当于一个数组名,保存的是第一个字节的地址

*pArr = 4;//等价于   array[0] = 4
pArr[1] = 5;  //等价于    array[1] = 5free(pArr);//释放pArr所占用的内存空间,程序不再具备对这个内存空间的控制权

}
动态输入输出数组1:
#include <stdio.h>
#include <string.h>
#include <malloc.h>

int main(void)
{
int len,i,j;
int array[5] = {1,2,3,4,5}; //静态内存分配,程序终止则释放,不可以手动释放

printf("请输入你需要的数组的长度:\n");
scanf("%d",&len);int * pArr = (int *)malloc(sizeof(int) * len); //动态分配内存
//数组输入
for(i=0;i<len;i++)
{int val;printf("请输入第%d个数组元素:\n",i+1);scanf("%d",&val);pArr[i] = val;
}//数组输出
printf("\n\n");
printf("数组展示:");
for(j=0;j<len;j++)
{printf("%d    ",pArr[j]);
}

}

动态输入输出数组2:(函数实现)
#include <stdio.h>
#include <string.h>
#include <malloc.h>

void get(int * pArr, int len);//函数声明
void push(int * pArr,int len);
int main(void)
{
int len;
int array[5] = {1,2,3,4,5};

printf("请输入你需要的数组的长度:\n");
scanf("%d",&len);int * pArr = (int *)malloc(sizeof(int) * len);
//数组输入
get(pArr,len);//数组输出
printf("\n\n");
push(pArr,len);

}
void get(int * pArr,int len)
{
int i;
for(i=0;i<len;i++)
{
int val;
printf(“请输入第%d个数组元素:\n”,i+1);
scanf("%d",&val);
pArr[i] = val;
}
}
void push(int * pArr,int len )
{
int j;
printf(“数组展示:”);
for(j=0;j<len;j++)
{
printf("%d ",pArr[j]);
}
}
课时 10、P10_跨函数使用内存讲解及其示例
1、在mian函数种调用其他函数的时候,其他函数是不可以改变main函数内部的数据的(因为定义在函数的内部的是局部变量),函数在调用时,系统会给函数分配空间,给函数内部的变量分配空间,所有静态分配的内存在函数执行完毕之后会被自动释放,但是动态分配的内存不会随着函数的终止而被释放,因为动态分配的内存是要用free()手动释放;
2、调用函数不可以修改其他函数内部的局部变量(除非通过指针),因为普通变量不可以跨函数使用内存,但可以通过return返回数据,但只能返回一次,因为函数一旦遇到return语句就会被终止;
3、局部变量不可以跨函数使用内存
代码:
#include <stdio.h>
#include <string.h>
#include <malloc.h>

void f(int i);//前置声明
int g(int i);
int main(void)
{
int i = 10;//i是mian函数的局部变量
printf(“i = %d\n”,i);

f(i);
printf("i = %d\n",i);i = g(i);
printf("i = %d\n",i);

}
void f(int i)//i是f函数的局部变量
{
i = 88;
}

int g(int i){
i = 99;
return i;
}
/*
输出结果:
i = 10
i = 10
i = 99
*/
4、正确答案:C项

A项:指针变量P没有进行初始化,内部存放的是一个垃圾地址,不可以对P进行操作;
B项:p是指针变量,是int * 类型,&p是int **类型;p没有进行初始化,fun函数要求传入的是int **类型的数据,用于存放指针变量的地址,所以可以传入&p,fun函数将int类型的s的地址赋值给
q(*q就代表p),改变了p内部存放的地址,相当于进行了初始化的操作,但是由于s是fun函数内部定义的局部变量,所以函数执行完毕之后会被释放,所以p没有指向稳定的内存空间;
C项:与B项类似,但是fun函数内部是动态分配内存,所以内存并不会因为函数的终止而被释放,所以p指向了稳定的内存空间,malloc分配的动态内存必须通过free()函数才能被释放;
注意:动态分配内存要通过程序员手动释放,否则只有等到程序完全终止,动态分配的内存才有可能被释放,因为程序员可能了遗忘释放内存,所以造成了内存空间越用越少,这种情况称为内存泄漏;
在Java种不用动态分配内存和手动释放内存,因为Java存在虚拟机,内部有自动回收内存的装置;
D项:fun函数的p与mian函数的p,两个指针变量指向同一内存空间,但是fun函数的p改变的是局部指针变量p的地址,并非main函数内部的指针变量p的地址;
5、我们要跨函数使用内存,改变其他的函数内部的数据,必须要通过指针(根据 *指针变量名 )来改变;
6、跨函数使用 例子:
CreateList()函数创建链表是动态创建的,所以当函数执行完毕之后并不会被释放,所以在main函数中我们还可以通过调用其他的函数来输出动态创建的链表;

7、链表例子:
#include <stdio.h>
#include <malloc.h>

struct Student * CreateList();//前置声明
void show(struct Student * pst);
struct Student {
int num;
int age;
};
int main(void)
{
struct Student * p;

p = CreateList();//定义链表
show(p);//输出链表

}
struct Student * CreateList()//struct Student * 表示返回值的类型 CreateList是函数名称
{
//要求系统动态分配struct Student类型的大小的内存空间
//struct Student *表示动态分配的内存以struct Student 类型划分
//返回的是所分配的内存空间的第一个字节的地址 ,即用于存放链表
struct Student * ps = (struct Student *)malloc(sizeof(struct Student));
//链表数据赋值
ps->num = 10001;
ps->age = 18;

return ps;

}
void show(struct Student * pst)
{
//pst和p都指向同一块内存空间,存放的字节地址也是相同的
printf("%d %d\n",pst->num,(*pst).age);
}

课时 11、P11_复习
1、什么是数据结构?
研究数据的存储问题;

2、算法:研究数据的操作;
3、不同的存储方式对应的操作不一样(例如数组和链表的存储方式不同,取值方式也不同);
4、衡量算法

课时 12、P12_连续存储数组的算法演示_1
1、什么是线性结构?
(结点类似于数组中的某个元素,即逻辑上具有独立意义的一个个体)
把所有的结点用一根直线穿起来;数组和链表都是线性结构;
2、线性结构分类:
1)、连续存储(数组)
普通的数组的定义方法:

什么叫数组?
元素类型相同,大小相等;
数组的优缺点?

特别注意:
当我们在结构体中定义了各种成员时,也定义了指针pBase,他们是结构体的成员变量;
当用户调用初始化函数时:我们请求系统动态分配一段用户需要大小的内存空间,并用pBase指向这段空间,pBase内部存储的是这段空间的第一个字节的地址,系统分配的这段内存用于存储我们的数组元素,与结构体变量的空间是两个不同的独立的空间,而pBase只是为了方便的输出元素数据来指向该段内存空间的指针变量;
结构体成员:数组长度、数组有效数据的个数等等都是为了记录数组的状态而已,在输出或者判断数组的情况的时候作为参数,和存储数组数据的内存空间相互独立;
图示:

本节课代码(不完善):
#include <stdio.h>
#include <malloc.h>//包含了malloc函数
#include <stdlib.h>//包含了exit函数 用于终止程序

//定义了一个数据类型,数组类型名为: struct Arr
struct Arr
{
//以下为数据类型的成员
int * pBase;//存储的是数组的第一个元素的地址
int len;//数组所能容纳的最大元素的个数
int cnt;//当前数组的有效元素的个数
};

//在C语言中布尔类型是bool 在Java中布尔类型是Boolean
void init_arr(struct Arr * pArr,int length);//数组初始化
bool append_arr();//在数组末尾追加元素
bool insert_arr();//在数组中插入元素
bool delete_arr();//删除元素
int get();//获取数组中的某个元素
bool is_empty(struct Arr * pArr); //判断数组是否为空
bool is_full();//判断数组是否为满
void sort_arr();//数组排序
void show_arr (struct Arr * pArr);//数组输出
void inversion_arr();//数组倒置
int main(void)
{
//定义数据类型的变量,已经分配了内存,但是内部未初始化,存放的是垃圾数据
struct Arr arr;
init_arr(&arr,6);//初始化数组,传入需要初始化的数组作为参数
// 输出数组可以采取直接传入变量输出 和 利用指针变量输出 两种方式,但是后者所占字节更少,省内存
show_arr(&arr);//输出数组

// printf("%d\n",arr.len);
}

void init_arr(struct Arr * pArr,int length)
{//指针变量只占4个字节 ,要通过其他不同函数改变变量的值必须使用指针 ,pArr指向arr
//动态分配内存,使得arr的指针指向分配给arr的存放数组元素的内存空间
//存放数组元素的内存空间与结构体变量的空间相互独立
//因为struct Arr类型 成员是int类型,所以以int划分。并且pBase默认指向第一个元素,存储分配空间的第一个字节地址
pArr->pBase = (int *)malloc(sizeof(int) * length);// pArr->pBase 等价于 (*pArr).pBase 等价于 arr.pBase

//但如果计算机内存不够 ,或者分配内存不成功 ,计算机会把NULL赋值给pBase,所以我们需要判断分配地址是否为NULL
//NULL地址部分不可读不可写
if(NULL == pArr->pBase) //判断指针内部存放的地址是否为NULL
{
printf(“动态内存分配失败!\n”);
exit(-1);//表示终止整个程序
}
else{//动态内存分配成功后,对结构体变量进行初始化
pArr->len = length;
pArr->cnt = 0;//用户还没赋值,有效数据为0
}
return;//返回空值,为了终止函数
}
bool is_empty(struct Arr * pArr)
{//用cnt有效数据的个数来判断 ,pArr 指向结构体变量与show_arr函数的指针变量指向相同
if (0 == pArr->cnt)
{
return true;
}
else
{
return false;
}
}
void show_arr (struct Arr * pArr)
{
//在输出数组的时候我们需要判断数组是否为空,如果为空,让用户输入数值,若不为空,输出数组数据
if( is_empty(pArr) )
{
printf(“数组为空!\n”);
}
else{
for( int i=0; icnt; i++)
{
//pArr是指向结构体变量,pBase是结构体变量内部的指针,指向的是动态分配的内存的第一个元素
//pArr->pBase代表的就是arr.pBase 内部存放的是数组元素的第一个字节的地址
//pArr->pBase[i] 相当于 arr.pBase[i] 相当于 数组名[i] 数组名存放的就是第一个元素的地址,是普通常量
printf("%d ",pArr->pBase[i]);
}
}

}

2)、离散存储(链表)
课时 13、P13_连续存储数组的算法演示_2
1、用insert插入数据时,传入的参数是:内存单元地址、插入元素的地址,插入的元素
从右往左依次移动,避免覆盖
流程操作:

2、删除元素,从左往右依次移动,避免覆盖

3、数组倒置,就是交换元素

4、排序函数----冒泡排序,两两比较

数组的应用代码:
#include <stdio.h>
#include <malloc.h>//包含了malloc函数
#include <stdlib.h>//包含了exit函数 用于终止程序

//定义了一个数据类型,数组类型名为: struct Arr
struct Arr
{
//以下为数据类型的成员
int * pBase;//存储的是数组的第一个元素的地址
int len;//数组所能容纳的最大元素的个数
int cnt;//当前数组的有效元素的个数
};

//在C语言中布尔类型是bool 在Java中布尔类型是Boolean
void init_arr(struct Arr * pArr,int length);//数组初始化
bool append_arr(struct Arr * pArr,int val);//在数组末尾追加元素
bool insert_arr(struct Arr * pArr,int pos,int val);//在数组中插入元素
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(struct Arr * pArr);//数组排序
void show_arr (struct Arr * pArr);//数组输出
void inversion_arr(struct Arr * pArr);//数组倒置
int main(void)
{
//定义数据类型的变量,已经分配了内存,但是内部没有初始化,存放的是垃圾数据
struct Arr arr;
init_arr(&arr,6);//初始化数组,传入需要初始化的数组作为参数
// 输出数组可以采取直接传入变量输出 和 利用指针变量输出 两种方式,但是后者所占字节更少,省内存
show_arr(&arr);//输出数组

//插入元素,当数组溢出时,不能在继续插入元素
append_arr(&arr,1);
append_arr(&arr,7);
append_arr(&arr,22);
append_arr(&arr,4);
insert_arr(&arr,1,99);
insert_arr(&arr,6,88); //当前插入在数组的最后,cnt是5 show_arr(&arr);//输出数组 int val;
//val的存储的是所删除的元素,函数内部将所删除的元素赋值给了val
delete_arr(&arr,1,&val);
printf("\n删除的数组元素的值为:%d\n",val );
show_arr(&arr);//输出数组 inversion_arr(&arr);
printf("\n倒置数组:\n");
show_arr(&arr);//输出数组 sort_arr(&arr);
printf("\n排序数组:\n");
show_arr(&arr);//输出数组

}

void init_arr(struct Arr * pArr,int length)
{//指针变量只占4个字节 ,要通过其他不同函数改变变量的值必须使用指针 ,pArr指向arr
//动态分配内存,使得arr的指针指向分配给arr的存放数组元素的内存空间
//存放数组元素的内存空间与结构体变量的空间相互独立
//因为struct Arr类型 成员是int类型,所以以int划分。并且pBase默认指向第一个元素,存储分配空间的第一个字节地址
//malloc函数返回的是所分配的内存空间的第一个字节的地址,所以使用指针变量接收
//指针变量占四个字节,用于存放地址
pArr->pBase = (int *)malloc(sizeof(int) * length);// pArr->pBase 等价于 (*pArr).pBase 等价于 arr.pBase

//但如果计算机内存不够 ,或者分配内存不成功 ,计算机会把NULL赋值给pBase,所以我们需要判断分配地址是否为NULL
//NULL地址部分不可读不可写
if(NULL == pArr->pBase) //判断指针内部存放的地址是否为NULL
{printf("动态内存分配失败!\n"); exit(-1);//表示终止整个程序
}
else{//动态内存分配成功后,对结构体变量进行初始化 pArr->len = length;pArr->cnt = 0;//用户还没赋值,有效数据为0
}
return;//返回空值,为了终止函数

}
bool is_empty(struct Arr * pArr)
{//用cnt有效数据的个数来判断 ,pArr 指向结构体变量与show_arr函数的指针变量指向相同
if (0 == pArr->cnt)
{
return true;
}
else
{
return false;
}
}
bool is_full(struct Arr * pArr)
{//在插入元素之前判断数组是否已满
if(pArr->cnt == pArr->len)
{
return true;
}
else{
return false;
}
}
void show_arr (struct Arr * pArr)
{
//在输出数组的时候我们需要判断数组是否为空,如果为空,让用户输入数值,若不为空,输出数组数据
if( is_empty(pArr) )
{
printf(“数组为空!\n”);
}
else{
for( int i=0; icnt; i++)
{
//pArr是指向结构体变量,pBase是结构体变量内部的指针,指向的是动态分配的内存的第一个元素
//pArr->pBase代表的就是arr.pBase 内部存放的是数组元素的第一个字节的地址
//pArr->pBase[i] 相当于 arr.pBase[i] 相当于 数组名[i] 数组名存放的就是第一个元素的地址,是普通常量
printf("%d ",pArr->pBase[i]);
}
}

}
bool append_arr(struct Arr * pArr,int val)
{//插入数组元素,根据指针指向数组,和需要插入的元素的值,在数组后面添加元素
if( is_full(pArr) )
{//数组已满的情况
return false;
}
else{//数组未满,进行插入值
//cnt表示当前内部有效元素的个数,当数组中存放了一个有效元素时,cnt为1,那我们插入的这个元素应该放在下标为1的位置上,刚好就是cnt代表的数
pArr->pBase[pArr->cnt] = val; //元素赋值
pArr->cnt++;//赋值成功后元素的有效数据个数加一
return true;//表示元素数值成功,终止函数
}
}
bool insert_arr(struct Arr * pArr,int pos,int val)
{//参数含义:第一个指针变量指向数组;第二个pos表示数据插入的位置; 第三个val是插入的元素
//默认将数据插入到pos地址的前面,如pos是1,则将数据插入到下标为1的数据的前面
if( is_full(pArr) )//判断数组是否为满
{
return false;
}

//数组的下标不能为负数,第一个元素使用下标为1,存放下标为0
//插入的元素的位置最多是最后一个元素的后面,所以为cnt+1    避免用户输入无效值
if(pos<1 || pos>pArr->cnt+1) //||或运算符 ,有真则为真
{return false;
}int i;
for(i=pArr->cnt-1; i>=pos-1 ;i--)//当循环到最后一项,交换的就是pos-1
{//元素移动应该是从右往左依次移动 pArr->pBase[i+1] = pArr->pBase[i];//元素后移
}
pArr->pBase[pos-1] = val;//元素插入
(pArr->cnt)++ ;//插入数据后有效数据的个数增加
return true;//元素插入成功,终止函数

// printf(“666”);
}
bool delete_arr(struct Arr * pArr,int pos,int * pVal)
{// 指针变量val的传入只是为了存储我们删除的哪个元素的地址,以便在mian函数中可以使用
if( is_empty(pArr) )//判断数组是否为空,若为空则没有元素可以删除
{
return false;
}

//删除的元素的下标必定大于1,小于数组中的最有一个有效数据的地址
if(pos<1 || pos>pArr->cnt) //||或运算符 ,有真则为真
{return false;
}*pVal =  pArr->pBase[pos-1];//pos和数组中的顺序有差异 ,将该元素保存在变量val中
int i;
for(i=pos; i<pArr->cnt; i++)
{//元素移动应该是从左往右依次移动 pArr->pBase[i-1] = pArr->pBase[i];//前移元素
}
pArr->cnt--;
return true;

}
void inversion_arr(struct Arr *pArr)
{//实现数组两端元素的依次互换即可
int i = 0;
int j = pArr->cnt-1;//数组下标从0开始
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,j,t;
for(i=0;icnt-1;i++)
{//i只能取倒数第二个值
for(j=i+1;jcnt;j++)
{//j可以取最后一个值
if(pArr->pBase[i] > pArr->pBase[j])
{
// printf("%d",pArr->pBase[j+1]);
t = pArr->pBase[i] ;
pArr->pBase[i] = pArr->pBase[j];
pArr->pBase[j] = t;
}
}
}
}

课时 14、P14_链表的重要性
1、基础且重要
课时 15、P15_typedef的用法
1、typedef用于给数据类型取别名,方便使用 ;
格式: typedef 数据类型名 别名;
代码:
#include <stdio.h>

//typedef表示用于定义别名,此处将int 的别名定义为 Next
typedef int Next;

typedef struct Student
{
int num;
int age;
}STU;//struct Student的别名为 STU 注意分号不可以少
int main(void)
{
int i = 10;
Next j = 88;//等价于 : int j = 88;
printf(“i = %d j = %d\n”,i,j);//输出结果:i = 10 j = 88

//定义一个 struct Student类型的变量
struct Student st ={10001, 18};
printf("num : %d    age : %d\n",st.num,st.age);STU st2;
st2.num = 10002;
st2.age = 22;
printf("num : %d    age : %d",st2.num,st2.age);

}
输出结果:

2、给数据类型的指针变量类型定义别名
#include <stdio.h>

typedef struct Student
{
int num;
int age;
}* PST;//PST 等价于 struct Student *
//PST代表的是 struct Student *的别名,而struct Student 没有定义别名
int main(void)
{
//定义一个 struct Student类型的变量
struct Student st;
// 定义一个 struct Student *类型的变量,并指向struct Student 类型变量st

PST ps = &st;
ps->num = 10001;
ps->age = 18;
//输出结果:num : 10001    age : 18
printf("num : %d    age : %d\n",st.num,st.age);

}
3、同时给结构体和结构体类型的指针变量取别名
#include <stdio.h>

typedef struct Student
{
int num;
int age;
}* PST,ST;//PST 等价于 struct Student *
//PST代表的是 struct Student *的别名,而struct Student 没有定义别名
//ST代表的是 struct Student 的别名
int main(void)
{
//定义一个 struct Student类型的变量
ST st;
// 定义一个 struct Student *类型的变量,并指向struct Student 类型变量st
PST ps = &st;
ps->num = 10001;
ps->age = 18;
//输出结果:num : 10001 age : 18
printf(“num : %d age : %d\n”,st.num,st.age);
}
课时 16、P16_链表的定义
1、离散存储(链表)
定义:
n个节点离散分配;彼此通过指针相连;每个节点只有一个前驱节点,每个节点只有一个后续节点首节点没有前驱节点,尾节点没有后续节点;
专业术语:
首节点:第一个有效的节点;
尾节点:最后一个有效的节点;
头结点:第一个有效节点前的节点,不存放有效的数据
头结点存在的目的:为了方便对链表的操作
头结点的数据类型和首节点的类型一样
头指针:指向头结点的指针变量;内部存放头结点的地址;
尾指针:指向尾结点的指针变量;内部存放尾节点的地址;

分类:

算法:

链表的优缺点:

课时 17、P17_如果希望通过一个函数来对链表进行处理,我们至少需要接受链表的哪些参数
1、确定一个链表需要几个参数?
只需要一个参数,只需要头指针即可,头指针可以找到头结点,头结点可以找到首节点,则链表就被找到了;
此外头指针的大小只有四个字节的大小,而不同链表的头结点的大小可能不同,所以大小不同,所以不适合传头结点作为参数传递来确定链表;
课时 18、P18_每一个链表节点的数据类型该如何表示的问题
1、链表的节点分为两个部分:数据域 、 指针域
数据域:用于存放数据
指针域:存放下一个节点的地址(指向下一个节点)

课时 19、P19_链表的分类
接课时16:
分类:
单链表:
双链表:
每一个节点有两个指针域,左边指针域指向上一个节点,右边指针域指向下一个节点;

循环链表:
能通过任何一个节点找到其他所有的节点;(形成一个环,最后一个节点的指针域指向的是第一个节点)
非循环链表:
课时 20、P20_非循环单链表插入节点伪算法讲解
接课时19:
算法:
遍历:
查找:
清空:
销毁:
求长度:
排序:
删除节点:
插入节点:
链表的优缺点:
1、插入节点:改变链表中的节点的指针域的指向
代码:
q->pNext = p->pNext;//两个节点都指向节点2
p->pNext = q;//q是指针变量,内部存放的是节点x的地址,所有节点1指向了节点x

课时 21、P21_删除非循环单链表节点伪算法的讲解
1、删除节点

2、free(指针变量名) 释放的是指针变量所指向的那段内存空间的,并不是释放指针变量本身的内存空间;
3、p->pNext 表示的是指针变量p所指向的内存空间变量的pNext成员;
4、改变节点指针域的指向能否写为一下的情况?(p是指向该节点的指针变量)
p->pNext = p->pNext->pNext
不能,可能会造成内存空间的泄露
课时 22、P22_学习数据结构的目的和要达到的要求
1、要能写出链表
2、要看得懂伪算法
课时 23、P23_复习
1、存储的方式
线性结构(可以串联起来)
连续存储(数组)
离散存储(链表)
非线性结构
2、链表分为:单链表、双链表、循环链表、非循环链表
课时 24、P24_链表创建和链表遍历算法的演示
1、创建链表和输出链表
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

//链表节点的数据类型结构体
typedef struct Node
{
int data;//数据域
struct Node * pNext;//指针域
}NODE , *PNODE;
//NODE代表的是struct Node类型的别名 , PNODE代表struct Node *类型的别名

PNODE create_list(void);//前置声明
void traverse_list(PNODE pHead);

int main(void)
{
//定义头指针 ,内部存放头节点的地址
PNODE pHead = NULL;//等价于struct Node * pHead = NULL; 定义结构体指针类型变量

//创建链表函数,并返回头结点地址
pHead = create_list(); //输出链表,传入头指针作为参数
traverse_list(pHead);

}

PNODE create_list(void)//返回的参数是指针类型
{
int len;
int val;
int i;

//定义头结点
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if(NULL == pHead)//判断系统分配内存是否成功
{printf("内存分配失败!\n");exit(-1);
} //pTrail用于指向尾节点,便于在挂节点的时候可以依次使得链表节点指向其后一个节点
PNODE pTrail;
pTrail = pHead;//使得pTrail与pHead的指向相同,都指向头结点

pTrail->pNext = NULL;//避免当用户要求链表长度为0时,没有初始化造成头结点内部是垃圾地址,

printf("请输出链表节点的个数:\n");
scanf("%d",&len);
//创建节点并挂在头结点的后面
for(i=0; i<len;i++)
{printf("请输出第%d个元素的值:\n",i+1);scanf("%d",&val);//创建当前需要插入链表的节点,并且用pNew指针指向该节点 PNODE pNew = (PNODE)malloc(sizeof(NODE));if(NULL == pNew){printf("内存分配失败!\n");exit(-1);} pNew->data = val;//首节点存放是用户插入的节点数据 pTrail->pNext = pNew;//头结点指向首节点 pTrail = pNew;

pNew->pNext = NULL;//将当前节点指针域设置为空,因为就目前的链表来看,当前节点就是尾节点

}
return pHead;//返回头节点地址

}

void traverse_list(PNODE pHead)//此处的pHead与mian函数中的pHead是各自函数的局部变量,但是传参时两者指向同一个内存空间
{
PNODE p = pHead->pNext;//p指向首节点,因为头结点不存放有效数据
printf("\n");
while(NULL != p)//p内部存放的是节点的地址,当遇到尾节点时,尾节点的指针域为空,赋值时使得p也是NULL,则不满足条件
{
printf("%d “,p->data);
p = p->pNext;//指向下一个节点
}
printf(”\n");
}

课时 25、P25_判断链表是否为空 和 求链表长度 算法的演示
1、代码:
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

//链表节点的数据类型结构体
typedef struct Node
{
int data;//数据域
struct Node * pNext;//指针域
}NODE , *PNODE;
//NODE代表的是struct Node类型的别名 , PNODE代表struct Node *类型的别名

PNODE create_list(void);//前置声明
void traverse_list(PNODE pHead);
bool is_empty(PNODE pHead);//判断链表是否为空
int length_list(PNODE pHead);//判断链表的长度;需要传入判断链表的头指针确定链表
bool insert_list(PNODE pHead,int pos,int val);//插入节点;传入操作的链表;插入节点的位置;插入节点元素值
bool delete_list(PNODE pHead,int pos,int * val);//删除节点 ;传入操作的链表;删除节点的位置;返回删除的值,让mian函数可以使用
void sort_list(PNODE pHead);//链表排序

int main(void)
{
//定义头指针 ,内部存放头节点的地址
PNODE pHead = NULL;//等价于struct Node * pHead = NULL; 定义结构体指针类型变量

//创建链表函数,并返回头结点地址
pHead = create_list(); //输出链表,传入头指针作为参数
traverse_list(pHead); //判断链表是否为空,返回的是布尔值 if( is_empty(pHead) ){printf("链表为空!\n");}else{printf("链表不为空!\n");}//查询链表长度int j = length_list(pHead);printf("\n链表的长度为:%d\n",j);

}

PNODE create_list(void)//返回的参数是指针类型
{
int len;
int val;
int i;

//定义头结点
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if(NULL == pHead)//判断系统分配内存是否成功
{printf("内存分配失败!\n");exit(-1);
} //pTrail用于指向尾节点,便于在挂节点的时候可以依次使得链表节点指向其后一个节点
PNODE pTrail;
pTrail = pHead;//使得pTrail与pHead的指向相同,都指向头结点
pTrail->pNext = NULL;//避免当用户要求链表长度为0时,没有初始化造成头结点内部是垃圾地址,printf("请输出链表节点的个数:\n");
scanf("%d",&len);
//创建节点并挂在头结点的后面
for(i=0; i<len;i++)
{printf("请输出第%d个元素的值:\n",i+1);scanf("%d",&val);//创建当前需要插入链表的节点,并且用pNew指针指向该节点 PNODE pNew = (PNODE)malloc(sizeof(NODE));if(NULL == pNew){printf("内存分配失败!\n");exit(-1);} pNew->data = val;//首节点存放是用户插入的节点数据 pTrail->pNext = pNew;//头结点指向首节点 pTrail = pNew; pNew->pNext = NULL;//将当前节点指针域设置为空,因为就目前的链表来看,当前节点就是尾节点 }
return pHead;//返回头节点地址

}

void traverse_list(PNODE pHead)//此处的pHead与mian函数中的pHead是各自函数的局部变量,但是传参时两者指向同一个内存空间
{
PNODE p = pHead->pNext;//p指向首节点,因为头结点不存放有效数据
printf("\n");
while(NULL != p)//p内部存放的是节点的地址,当遇到尾节点时,尾节点的指针域为空,赋值时使得p也是NULL,则不满足条件
{
printf("%d “,p->data);
p = p->pNext;//指向下一个节点
}
printf(”\n");
}
bool is_empty(PNODE pHead)
{//当头结点指针域为空时,链表为空,因为首节点不存在
if(NULL == pHead->pNext)
{
return true;
}
else{
return false;
}
}
int length_list(PNODE pHead)
{
PNODE p = pHead->pNext;
int len = 0;
while(NULL != p)
{
len++;
p = p->pNext;
}
printf("\n");
return len;
}

课时 26、通过链表排序算法的演示 再次详细讨论到底什么是算法以及到底什么是泛型【重点】
1、数组的排序(前后两个元素相互比较,满足条件则交换位置)

代码解释:

2、链表的排序
链表和数组的存储方式都是不一样的,但是他们都属于线性结构;
算法:
狭义的算法是与数据的存储方式密切相关的;(内部具体操作不同)
广义的算法与数据的存储方式无关;(外部操作相同,只是内部细节不同)
泛型:利用某种技术达到的效果也就是:不同的存储方式,执行操作时是一样的

代码:
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

//链表节点的数据类型结构体
typedef struct Node
{
int data;//数据域
struct Node * pNext;//指针域
}NODE , *PNODE;
//NODE代表的是struct Node类型的别名 , PNODE代表struct Node *类型的别名

PNODE create_list(void);//前置声明
void traverse_list(PNODE pHead);
bool is_empty(PNODE pHead);//判断链表是否为空
int length_list(PNODE pHead);//判断链表的长度;需要传入判断链表的头指针确定链表
bool insert_list(PNODE pHead,int pos,int val);//插入节点;传入操作的链表;插入节点的位置;插入节点元素值
bool delete_list(PNODE pHead,int pos,int * val);//删除节点 ;传入操作的链表;删除节点的位置;返回删除的值,让mian函数可以使用
void sort_list(PNODE pHead);//链表排序

int main(void)
{
//定义头指针 ,内部存放头节点的地址
PNODE pHead = NULL;//等价于struct Node * pHead = NULL; 定义结构体指针类型变量

//创建链表函数,并返回头结点地址
pHead = create_list(); //输出链表,传入头指针作为参数
traverse_list(pHead); //判断链表是否为空,返回的是布尔值 if( is_empty(pHead) ){printf("链表为空!\n");}else{printf("链表不为空!\n");}//查询链表长度
int j = length_list(pHead);
printf("\n链表的长度为:%d\n",j); //链表排序,与数组的排序原理相似
sort_list(pHead); //输出链表,传入头指针作为参数
traverse_list(pHead);

}

PNODE create_list(void)//返回的参数是指针类型
{
int len;
int val;
int i;

//定义头结点
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if(NULL == pHead)//判断系统分配内存是否成功
{printf("内存分配失败!\n");exit(-1);
} //pTrail用于指向尾节点,便于在挂节点的时候可以依次使得链表节点指向其后一个节点
PNODE pTrail;
pTrail = pHead;//使得pTrail与pHead的指向相同,都指向头结点
pTrail->pNext = NULL;//避免当用户要求链表长度为0时,没有初始化造成头结点内部是垃圾地址,printf("请输出链表节点的个数:\n");
scanf("%d",&len);
//创建节点并挂在头结点的后面
for(i=0; i<len;i++)
{printf("请输出第%d个元素的值:\n",i+1);scanf("%d",&val);//创建当前需要插入链表的节点,并且用pNew指针指向该节点 PNODE pNew = (PNODE)malloc(sizeof(NODE));if(NULL == pNew){printf("内存分配失败!\n");exit(-1);} pNew->data = val;//首节点存放是用户插入的节点数据 pTrail->pNext = pNew;//头结点指向首节点 pTrail = pNew; pNew->pNext = NULL;//将当前节点指针域设置为空,因为就目前的链表来看,当前节点就是尾节点 }
return pHead;//返回头节点地址

}

void traverse_list(PNODE pHead)//此处的pHead与mian函数中的pHead是各自函数的局部变量,但是传参时两者指向同一个内存空间
{
PNODE p = pHead->pNext;//p指向首节点,因为头结点不存放有效数据
printf("\n");
while(NULL != p)//p内部存放的是节点的地址,当遇到尾节点时,尾节点的指针域为空,赋值时使得p也是NULL,则不满足条件
{
printf("%d “,p->data);
p = p->pNext;//指向下一个节点
}
printf(”\n");
}
bool is_empty(PNODE pHead)
{//当头结点指针域为空时,链表为空,因为首节点不存在
if(NULL == pHead->pNext)
{
return true;
}
else{
return false;
}
}
int length_list(PNODE pHead)
{
PNODE p = pHead->pNext;
int len = 0;
while(NULL != p)
{
len++;
p = p->pNext;
}
printf("\n");
return len;
}
void sort_list(PNODE pHead)//与数组的排序原理相似,两者都是线性结构
{
int i,j,len,t;
len = length_list(pHead);//获取链表的长度
PNODE p,q;//两个指针用于存放需要比较的两个链表节点的地址

//pHead是头结点的地址,pHead->pNext是首节点的地址
for(i=0,p=pHead->pNext; i<len-1; i++,p=p->pNext)
{for(j=i+1,q=p->pNext; j<len; j++,q=q->pNext){if(p->data > q->data)//类似于if(a[i] > a[j]){t = p->data;//t = a[i];p->data = q->data;//a[i] = a[j];q->data = t;//a[j] = t;} }
}

}

课时 27、P27_如何学习算法自己的一些感想
看懂程序:
流程
每个语句功能
试数
课时 28、P28_链表插入和删除算法的演示
1、在链表中插入节点元素
部分图解:

代码:
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

//链表节点的数据类型结构体
typedef struct Node
{
int data;//数据域
struct Node * pNext;//指针域
}NODE , *PNODE;
//NODE代表的是struct Node类型的别名 , PNODE代表struct Node *类型的别名

PNODE create_list(void);//前置声明
void traverse_list(PNODE pHead);
bool is_empty(PNODE pHead);//判断链表是否为空
int length_list(PNODE pHead);//判断链表的长度;需要传入判断链表的头指针确定链表
bool insert_list(PNODE pHead,int pos,int val);//插入节点;传入操作的链表;插入节点的位置;插入节点元素值
bool delete_list(PNODE pHead,int pos,int * val);//删除节点 ;传入操作的链表;删除节点的位置;返回删除的值,让mian函数可以使用
void sort_list(PNODE pHead);//链表排序

int main(void)
{
//定义头指针 ,内部存放头节点的地址
PNODE pHead = NULL;//等价于struct Node * pHead = NULL; 定义结构体指针类型变量

//创建链表函数,并返回头结点地址
pHead = create_list(); //输出链表,传入头指针作为参数
traverse_list(pHead); //判断链表是否为空,返回的是布尔值 if( is_empty(pHead) ){printf("链表为空!\n");}else{printf("链表不为空!\n");}//查询链表长度
int j = length_list(pHead);
printf("\n链表的长度为:%d\n",j); insert_list(pHead,4,99);
//输出链表,传入头指针作为参数
printf("\n插入元素后的链表\n");
traverse_list(pHead); //链表排序,与数组的排序原理相似
sort_list(pHead);
//输出链表,传入头指针作为参数
printf("\n排序后的链表\n");
traverse_list(pHead);

}

PNODE create_list(void)//返回的参数是指针类型
{
int len;
int val;
int i;

//定义头结点
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if(NULL == pHead)//判断系统分配内存是否成功
{printf("内存分配失败!\n");exit(-1);
} //pTrail用于指向尾节点,便于在挂节点的时候可以依次使得链表节点指向其后一个节点
PNODE pTrail;
pTrail = pHead;//使得pTrail与pHead的指向相同,都指向头结点
pTrail->pNext = NULL;//避免当用户要求链表长度为0时,没有初始化造成头结点内部是垃圾地址,printf("请输出链表节点的个数:\n");
scanf("%d",&len);
//创建节点并挂在头结点的后面
for(i=0; i<len;i++)
{printf("请输出第%d个元素的值:\n",i+1);scanf("%d",&val);//创建当前需要插入链表的节点,并且用pNew指针指向该节点 PNODE pNew = (PNODE)malloc(sizeof(NODE));if(NULL == pNew){printf("内存分配失败!\n");exit(-1);} pNew->data = val;//首节点存放是用户插入的节点数据 pTrail->pNext = pNew;//头结点指向首节点 pTrail = pNew; pNew->pNext = NULL;//将当前节点指针域设置为空,因为就目前的链表来看,当前节点就是尾节点 }
return pHead;//返回头节点地址

}

void traverse_list(PNODE pHead)//此处的pHead与mian函数中的pHead是各自函数的局部变量,但是传参时两者指向同一个内存空间
{
PNODE p = pHead->pNext;//p指向首节点,因为头结点不存放有效数据
printf("\n");
while(NULL != p)//p内部存放的是节点的地址,当遇到尾节点时,尾节点的指针域为空,赋值时使得p也是NULL,则不满足条件
{
printf("%d “,p->data);
p = p->pNext;//指向下一个节点
}
printf(”\n");
}
bool is_empty(PNODE pHead)
{//当头结点指针域为空时,链表为空,因为首节点不存在
if(NULL == pHead->pNext)
{
return true;
}
else{
return false;
}
}
int length_list(PNODE pHead)
{
PNODE p = pHead->pNext;
int len = 0;
while(NULL != p)
{
len++;
p = p->pNext;
}
printf("\n");
return len;
}
void sort_list(PNODE pHead)//与数组的排序原理相似,两者都是线性结构
{
int i,j,len,t;
len = length_list(pHead);//获取链表的长度
PNODE p,q;//两个指针用于存放需要比较的两个链表节点的地址

//pHead是头结点的地址,pHead->pNext是首节点的地址
for(i=0,p=pHead->pNext; i<len-1; i++,p=p->pNext)
{for(j=i+1,q=p->pNext; j<len; j++,q=q->pNext){if(p->data > q->data)//类似于if(a[i] > a[j]){t = p->data;//t = a[i];p->data = q->data;//a[i] = a[j];q->data = t;//a[j] = t;} }
}

}
bool insert_list(PNODE pHead,int pos,int val)
{
int i = 0;
PNODE p = pHead;

// 以下while语句和if语句代码使得指针p指向了当前需要插入的节点的位置的前面的哪个节点
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;return true;

}

注意:
节点的插入是在输入的位置的前面插入元素,例如目前链表的长度是4,那么输入插入节点的最大位置是5,因为5表示在链表的第五个节点第四个节点后面插入节点,即放在链表的最后作为尾节点,当时节点的下标是从1开始,所有不可以输入0,并且头结点不可以存放有效数据;
2、删除链表节点

代码:
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

//链表节点的数据类型结构体
typedef struct Node
{
int data;//数据域
struct Node * pNext;//指针域
}NODE , *PNODE;
//NODE代表的是struct Node类型的别名 , PNODE代表struct Node *类型的别名

PNODE create_list(void);//前置声明
void traverse_list(PNODE pHead);
bool is_empty(PNODE pHead);//判断链表是否为空
int length_list(PNODE pHead);//判断链表的长度;需要传入判断链表的头指针确定链表
bool insert_list(PNODE pHead,int pos,int val);//插入节点;传入操作的链表;插入节点的位置;插入节点元素值
bool delete_list(PNODE pHead,int pos,int * val);//删除节点 ;传入操作的链表;删除节点的位置;返回删除的值,让mian函数可以使用
void sort_list(PNODE pHead);//链表排序

int main(void)
{
//定义头指针 ,内部存放头节点的地址
PNODE pHead = NULL;//等价于struct Node * pHead = NULL; 定义结构体指针类型变量

//创建链表函数,并返回头结点地址
pHead = create_list(); //输出链表,传入头指针作为参数
traverse_list(pHead); //判断链表是否为空,返回的是布尔值 if( is_empty(pHead) ){printf("链表为空!\n");}else{printf("链表不为空!\n");}//查询链表长度
int j = length_list(pHead);
printf("\n链表的长度为:%d\n",j); insert_list(pHead,4,99);
//输出链表,传入头指针作为参数
printf("\n插入元素后的链表\n");
traverse_list(pHead); int val;//存放删除的元素
if( delete_list(pHead,1,&val) )
{printf("\n删除元素成功,删除的元素是:%d\n",val);
}
else
{printf("\n删除元素成功失败\n");
}
//输出链表,传入头指针作为参数
printf("\n删除元素后的链表\n");
traverse_list(pHead); //链表排序,与数组的排序原理相似
sort_list(pHead);
//输出链表,传入头指针作为参数
printf("\n排序后的链表\n");
traverse_list(pHead);

}

PNODE create_list(void)//返回的参数是指针类型
{
int len;
int val;
int i;

//定义头结点
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if(NULL == pHead)//判断系统分配内存是否成功
{printf("内存分配失败!\n");exit(-1);
} //pTrail用于指向尾节点,便于在挂节点的时候可以依次使得链表节点指向其后一个节点
PNODE pTrail;
pTrail = pHead;//使得pTrail与pHead的指向相同,都指向头结点
pTrail->pNext = NULL;//避免当用户要求链表长度为0时,没有初始化造成头结点内部是垃圾地址,printf("请输出链表节点的个数:\n");
scanf("%d",&len);
//创建节点并挂在头结点的后面
for(i=0; i<len;i++)
{printf("请输出第%d个元素的值:\n",i+1);scanf("%d",&val);//创建当前需要插入链表的节点,并且用pNew指针指向该节点 PNODE pNew = (PNODE)malloc(sizeof(NODE));if(NULL == pNew){printf("内存分配失败!\n");exit(-1);} pNew->data = val;//首节点存放是用户插入的节点数据 pTrail->pNext = pNew;//头结点指向首节点 pTrail = pNew; pNew->pNext = NULL;//将当前节点指针域设置为空,因为就目前的链表来看,当前节点就是尾节点 }
return pHead;//返回头节点地址

}

void traverse_list(PNODE pHead)//此处的pHead与mian函数中的pHead是各自函数的局部变量,但是传参时两者指向同一个内存空间
{
PNODE p = pHead->pNext;//p指向首节点,因为头结点不存放有效数据
printf("\n");
while(NULL != p)//p内部存放的是节点的地址,当遇到尾节点时,尾节点的指针域为空,赋值时使得p也是NULL,则不满足条件
{
printf("%d “,p->data);
p = p->pNext;//指向下一个节点
}
printf(”\n");
}
bool is_empty(PNODE pHead)
{//当头结点指针域为空时,链表为空,因为首节点不存在
if(NULL == pHead->pNext)
{
return true;
}
else{
return false;
}
}
int length_list(PNODE pHead)
{
PNODE p = pHead->pNext;
int len = 0;
while(NULL != p)
{
len++;
p = p->pNext;
}
printf("\n");
return len;
}
void sort_list(PNODE pHead)//与数组的排序原理相似,两者都是线性结构
{
int i,j,len,t;
len = length_list(pHead);//获取链表的长度
PNODE p,q;//两个指针用于存放需要比较的两个链表节点的地址

//pHead是头结点的地址,pHead->pNext是首节点的地址
for(i=0,p=pHead->pNext; i<len-1; i++,p=p->pNext)
{for(j=i+1,q=p->pNext; j<len; j++,q=q->pNext){if(p->data > q->data)//类似于if(a[i] > a[j]){t = p->data;//t = a[i];p->data = q->data;//a[i] = a[j];q->data = t;//a[j] = t;} }
}

}
bool insert_list(PNODE pHead,int pos,int val)
{
int i = 0;
PNODE p = pHead;

// 以下while语句和if语句代码使得指针p指向了当前需要插入的节点的位置的前面的哪个节点
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;//临时节点,q指向新加入节点的后面的节点
p->pNext = pNew;// 新加入节点前面节点指向新加入节点
pNew->pNext = q;//新加入节点 指向 新加入节点的后面的节点 return true;

}
bool delete_list(PNODE pHead,int pos,int * Pval)
{
int i = 0;
PNODE p = pHead;

// 以下while语句和if语句代码使得指针p指向了当前需要删除的元素的前一个元素
while(NULL!=p && i<pos-1)
{p = p->pNext;++i;
}
if(i>pos-1 || NULL==p)
{return false;
}PNODE q = p->pNext;//q指针指向当前需要删除的哪个元素
* Pval = q->data;//存储删除的元素的数据//删除节点
p->pNext = q->pNext;//存放的是下下个元素的地址
free(q);//释放所删除的节点的内存空间
q = NULL;
return true;

}
课时 29、P29_复习
1、什么是数据结构?
狭义:数据结构是专门研究数据存储的问题;
数据的存储包含两方面:个体的存储、个体关系的存储;
广义:数据结构既包含数据的存储也包含数据的操作;
对数据的操作就是算法;
2、算法?
狭义:和数据的存储方式密切相关
广义:算法和数据的存储方式无关;(这就是泛型的思想)
3、数据的存储结构?
线性结构(可以用指针将数据串联起来)
连续存储(数组)
优点:存取速度很快;
缺点:插入删除元素很慢(需要移动其他元素,中间不能有空闲空间);
空间通常有限制;
需要事先知道数组的长度;
需要大块的连续内存;
离散存储(链表)
优点:空间无限制;
插入元素的速度快;
缺点:存取的速度很慢;
线性结构的应用:栈、队列
非线性结构


课时 30、P30_栈的定义
1、栈和堆是内存的两种分配方式
静态内存在栈中分配;动态内存在堆中分配;
栈和堆在内存中是两块内存区域;
2、栈
定义:一种可以实现 “先进后出” 的存储结构
栈类似于一个箱子
代码:

课时 31、P31_栈的分类
1、分类:
静态栈
动态栈
2、队列:先进先出原则

3、补充知识:静态栈和动态栈的区别
栈的实现需要确定顶部与底部.
静态栈与动态栈的区别:
静态栈必须提前确定栈的大小(有限的),并且都是连续的.
动态栈可以无限大小(内存够的情况下),并且是不连续的.
课时 32、P32_栈可以执行哪些操作
1、栈的算法
出栈------pop
入栈(压栈)-----push
课时 33、P33_栈程序演示(与链表相似)
1、用栈来创建链表(链式栈) (先进后出)
判断栈是否为空,判断pTop和pBottom的指向元素是否相同(判断内部存储的是都是同一段内存空间的地址)
链式栈不存在满栈的问题,就像链表一样,除非内存空间不够了,否则可以一直创建节点,加长链表;
当我们进行压栈操作时我们不能选择栈的放入元素的位置,因为压栈的操作只能在栈顶进行;
2、创建栈的思路(利用S内部的两个指针的指向来实现元素的压栈和遍历输出)
1)、初始化pTop和pBottom,使得两者最初都指向栈底的空间(不存放有效数据);

2)、当我们利用push函数将元素压栈之后,我们使得pTop指向当前创建的这个新元素空间;(这是pTop指向了当前的栈顶);

3)、每一个空间内部都有data部分课pNext部分,data部分存放的是当前栈元素的数据值。pNext指向其下面一个栈元素(存放下面一个栈元素的地址);

3、在通过遍历输出栈元素的时候,要新创建一个临时指针来从栈顶依次输出栈元素,pTop和pBottom的指向是不可以改变的;
4、pop函数用于逐个出栈元素,并返回出栈的元素的值,val利用指针特性将pop保存的栈元素的值返回给了main函数;
原理:

5、clear清空栈的函数

6、所有出栈的元素都需要进行free释放
代码:
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

typedef struct Node//链表节点的数据类型
{
int data;//数据域
struct Node * pNext;//指针域
}NODE, *PNODE;//别名

typedef struct Satck//栈的实现
{
PNODE pTop;//指向栈顶
PNODE pBottom;//指向栈底
}STACK, *PSTACK ;//创建别名

void init(PSTACK pS);//前置声明
void push(PSTACK pS,int val);//传入指向需要操作的栈的指针变量,val代表需要传入的值
void traverse(PSTACK pS);
bool pop(PSTACK pS,int * Pval);
bool clear(PSTACK pS);

int main(void)
{
STACK S;//等价于 struct Satck S; 创建一个指向栈的一块内存空间

init(&S);//初始化该 指针空间   指向栈 //利用指针来改变栈的内部的内容
push(&S,1);//压栈
push(&S,2);//压栈
push(&S,3);//压栈
printf("\n栈元素内容:");
traverse(&S); //遍历输出栈的内容 int val;
//输出栈元素,并接收输出的栈元素的值,返回值:是否成功输出   出栈元素内存将会被释放
if(pop(&S,&val))
{printf("\n出栈成功,出栈的元素是:%d\n",val);
}
else
{printf("出栈失败!");
}
printf("\n栈元素内容:");
traverse(&S); //遍历输出栈的内容 //清空栈元素
if( clear(&S) )
{printf("\n元素清空成功!");traverse(&S); //遍历输出栈的内容
}
else
{printf("\n元素清空失败");
}

}

void init(PSTACK pS)
{
//NODE是栈元素的内存空间,STACK用于指向栈元素
//创建栈的栈底的头元素,便于操作栈,不存放有效数据
pS->pTop = (PNODE)malloc(sizeof(NODE));
if(pS->pTop == NULL)//判断内存是否分配成功
{
printf(“动态内存分配失败!\n”);
exit(-1);
}
else
{
pS->pBottom = pS->pTop;//初始时使得两者同时指向栈底的头元素
//将新创建的栈底元素pNext初始化
pS->pTop->pNext = NULL;// 或者pS->pBottom->pNext = NULL
}
}
void push(PSTACK pS,int val)
{
//创建栈节点 创建的栈元素节点是struct Node类型,此处创建的指针是PNODE类型
PNODE pNew = (PNODE)malloc(sizeof(NODE));
pNew->data = val;
//将上一个栈元素的地址赋值给需要加入的栈元素的pNext
//此处的pTop不能改为pBottom,我们需要改变的是栈顶的指向
pNew->pNext = pS->pTop;
//使得pTop指向新的栈顶元素
pS->pTop = pNew;
}
void traverse(PSTACK pS)
{
//定义临时指针,指向栈顶元素
PNODE p = pS->pTop;
while(p != pS->pBottom) //当元素不是栈底头元素时,输出data
{
printf("%d ",p->data);
p = p->pNext;//指向下一个栈元素
}
return ;
}
//判断栈是否为空
bool empty(PSTACK pS)
{
if(pS->pTop == pS->pBottom)//栈空
{
return true;
}
else
{
return false;
}
}
//pS所指向的栈出栈依次,并把出栈的元素存入pVal形参所指向的变量中,如果栈为空则返回false
bool pop(PSTACK pS,int * Pval)
{
if( empty(pS) )//栈为空
{
return false;
}
//栈不为空时,元素出栈,逐步删除元素
PNODE p = pS->pTop;
* Pval = p->data;
pS->pTop = p->pNext;
free§;

return true;

}
bool clear(PSTACK pS)//清除所有的栈元素
{
PNODE p = pS->pTop;
PNODE q = NULL;

if( empty(pS) )//栈为空
{return false;
}
else{while(p != pS->pBottom) {q =  p->pNext;free(p);p = q; }pS->pTop = pS->pBottom;//栈已清空return true;
}

}

课时 34、P34_栈的日常具体应用
1、栈的应用
1)、函数调用:函数的调用和执行过程就是压栈和出栈的过程;
图示:

2)、中断
3)、表达式传值
4)、内存分配(静态内存就是在栈中分配d的)
5)、缓冲处理
6)、迷宫
课时 35、P35 _ 队列1 _ 什么是队列
1、定义:一种可以实现 “先进先出” 的存储结构;
课时 36、P36 _ 队列2 _ 队列的分类 和 链式队列伪算法的讲解
1、分类
链式队列 -----链表实现
静态队列 -----数组实现
2、链式队列图示

课时 37、P37 _ 队列3 _ 学习循环队列必须要弄清楚的7个问题概述
1、什么是静态队列?
与数组的功能相似(空间连续,头部只能输出元素,尾部只能输入元素)

2、静态队列一般都是循环队列
循环队列讲解:
1、静态队列为什么必须是循环队列?
2、循环队列幼几个参数来确定?
3、循环队列各个参数的含义?
4、循环队列入队伪算法讲解?
5、循环队列出队伪算法讲解?
6、如何判断循环队列是否为空?
7、如何判断循环队列是否已满?
课时 38、P38 _ 队列4 _ 静态队列为什么必须是循环队列
1、静态队列为什么是循环队列?
用数组实现静态队列图示:

循环队列方式的静态队列图示:

课时 39、P39 _ 队列5 _ 循环队列需要几个参数来确定 及其含义的讲解
1、循环队列需要几个参数来确定及其含义的讲解?
2个参数来确定,两个参数再不同的场合有不同的含义;(建议初学者先记住,然后慢慢体会)
这两个参数是 front 和 rear(指针参数)
课时 40、P40 _ 队列6 _ 循环队列各个参数的含义
1、各个参数的含义?(front和rear)
1)、队列初始化
front和rear的值都是零
2)、队列非空
front代表队列的第一个元素
rear代表的是队列的最后一个有效元素的下一个元素(空元素)
3)、队列空
front和rear的值相等,不一定是零
课时 41、P41 _ 队列7 _ 循环队列入队伪算法讲解
1、队尾入队(rear),队头出队(front)
2、入队伪算法
两步完成
1、将值存入r所代表的位置(r即为rear,f即为front)
2、错误写法:rear = rear+1;
正确写法:rear = (rear+1)% 数组的长度
在循环队列中的rear指针和front指针是循环移动的,当rear移到最后一个位置时,下一次如果还需要存入数据,那么r就会移到下标为0的空间;
r =(1+1)%6 = 2;
r = (2+1)%6 = 3
r = (3+1)%6 = 4;
r = (4+1)%6 = 5;
rear = n-1 % n = n-1;

课时 42、P42 _ 队列8 _ 循环队列出队伪算法讲解
1、循环队列出队伪算法
步骤:front = (front+1)%数组长度
课时 43、P43 _ 队列9 _ 如何判断循环队列是否为空
1、如何判断循环队列是否为空?
rear和front的值相同时(指向同一个元素),则该队列一定为空
课时 44、P44 _ 队列10 _ 如何判断循环队列是否已满
1、如何判断队列是否已满?
1)、front和rear的值的大小是不确定的

2)、如何判断队列已满?
预备知识:front和rear的值的大小不确定;
方式一:多增加一个标志参数(多增加一个参数来使得判断rear和front指向相同时到底是队列满了还是队列为空的情况)
方式二:少用一个元素(即当rear指向最后一个空间的时候,不允许再存入数据,浪费最后一块内存空间)(通常使用)伪算法图示如下:

课时 45、P4545 _ 复习 _ 求链表的长度
1、如何求链表的长度?
定义临时指针指向首节点,依次赋值 p = p->pNext 直到循环到尾节点(尾节点的pNext是NULL)时得出链表的长度;while p != NULL 时才是链表的有效元素;
课时 46、P46 _ 复习上节课队列知识
1、队列
定义:一种可以实现 先进先出 的存储结构
分类:静态队列(内部用数组实现)、链式队列(队头出,队尾入)
2、当队列中的n个元素都使用的时候,rear和front指向相同时并不能确定到底队列是满还是空,所以需要一个其他的参数来记录队列元素的情况;
3、因为队列空间全部使用时会导致无法判断队列的满和空,所以我们一般采取只存储n-1个元素的方式,浪费一个空间,此时front和rear指向相同时队列为空,rear指向最后一个元素时队列为满;
4、判断式:
front == rear 为空
(rear+1)%数组队列长度 == front 已满
注意:( n-1 )% n =n-1
5、front和rear移动(插入元素时往后移动rear,删除元素时往后移动front)
rear = (rear+1)%数组队列长度
front = (front+1)%数组队列长度
6、静态队列一定是循环队列(避免内存浪费)
7、循环队列需要2个参数确定:rear和front
8、队列初始化:rear和front都指向0;
队列为空:rear和front指向相同;
队列非空:front指向第一个有效元素,rear指向最后一个有效元素的后一个空闲空间;
9、入队:将数据存入rear指向的空间 和 rear = (rear+1)%数组队列长度
10、出队:将front指向的空间的元素删除 和 front = (front+1)%数组队列长度
课时 47、P47 _ 循环队列程序演示
1、rear和front只是数组队列的下标而并非指针!!!
2、队列操作的代码:
#include <stdio.h>
#include <malloc.h>

typedef struct Queue//指向队列的指针的类型
{
int * pBase;//pBase是指针用于操作数组队列
int front;//front和rear是数组的下标
int rear;
}QUEUE ;

//前置声明
void init(QUEUE * pQ);//初始化函数 。指向队列空间
bool en_queue(QUEUE * pQ,int val);//插入元素,参数:需要操作的队列 和 插入的值
void traverse_queue(QUEUE *pQ);//输出队列, 参数: 需要操作的队列
bool out_queue(QUEUE * pQ,int * Pval);//val传回出队元素

int main(void)
{
QUEUE Q;//定义出一段用于操作数组队列的空间
init(&Q);
en_queue(&Q,1);
en_queue(&Q,2);
en_queue(&Q,3);
en_queue(&Q,4);
en_queue(&Q,5);
en_queue(&Q,6);
traverse_queue(&Q);
printf("\n");

int val;
if(out_queue(&Q,&val))
{printf("出队成功!\n");printf("出队元素是: %d\n",val);
}
else
{printf("队列为空!\n");
}
printf("出队后的队列:\n");
traverse_queue(&Q);
printf("\n");

}

void init(QUEUE * pQ)
{
//初始化
//动态创建数组,pBase此刻就相当于数组名,指向数组的第一个元素空间,存放第一个字节地址
pQ->pBase = (int *)malloc(sizeof(int) * 6);
pQ->front = 0;
pQ->rear = 0;

}
bool full_queue(QUEUE * pQ)
{
if( (pQ->rear+1)%6 == pQ->front )
{//队列已满
return true;
}
else{
return false;
}
}
bool en_queue(QUEUE * pQ,int val)
{
if( full_queue(pQ) )
{//数组队列已满
return false;
}
else{
pQ->pBase[pQ->rear] = val;
pQ->rear = (pQ->rear+1) % 6; //后移rear
return true;
}
}
void traverse_queue(QUEUE *pQ)
{
//front的指向不能变,设置一个临时元素来循环输出队列即可
int i = pQ->front;
while( i != pQ->rear )//不为空
{
printf("%d ",pQ->pBase[i]);
i = (i+1) % 6;
}
}
bool empty_queue(QUEUE * pQ)
{
if( pQ->rear == pQ->front )
{//队列为空
return true;
}
else{
return false;
}
}
bool out_queue(QUEUE * pQ,int * Pval)
{
if( empty_queue(pQ) )
{
return false;
}
else
{
* Pval = pQ->pBase[pQ->front];
pQ->front = (pQ->front+1) % 6;
return true;
}
}

课时 48、P48 _ 队列的具体应用
1、队列的具体应用
所有和时间有关的操作都与队列有关
课时 49、P49 _ 可以不看
课时 50、P50 _ 递归1 _ 递归的定义 和 不同函数之间相互调 程序举例
1、定义:一个函数直接或者间接的调用自己;
递归是用栈来实现的;
2、递归程序代码:
#include <stdio.h>

void f();
void g();
void k();

void f()
{
printf(“FFFFF\n”);
g();
printf(“FFF!!!\n”);
}
void g()
{
printf(“GGGGG”);
k();
printf(“GGG!!!\n”);
}
void k()
{
printf(“KKKKK\n”);
printf(“KKK!!!\n”);
}

int main(void)
{
f();
printf(“MAIN!!!”);
}

代码图示:

课时 51、P51 _ 递归2 _ 一个函数自己调自己 程序举例
1、代码:
递归中的自我调用必须是满足一定条件的自我调用,而不是随意乱调用(无意义且可能是死递归)
#include <stdio.h>

void f(int n)
{
if(n == 1)
{
printf(“FFFFF\n”);//指挥输出一次
}
else
{
f(n-1);//调用自己
}
}

int main(void)
{
f(3);
}
课时 52、P52 _ 递归3 _ 1+2+3+…+100之和用递归来实现
1、求阶乘(for循环实现 或者 递归实现)
思路:n *(n-1)!
2、for循环求阶乘
代码:
#include <stdio.h>

int main(void)
{
int val;
int i,mul=1;

printf("请输入需要求阶乘的数:\n");
scanf("%d",&val);for(i=1; i<=val; i++)
{mul = mul*i;
}
printf("%d的阶乘是:%d\n",val,mul);

}
注意:我们定义的存储阶乘的数是int类型,所以当我们输入的数得到的阶乘的大小太大时,int类型会存储不下,此时会产生溢出的情况,导致队列出栈,得到的阶乘结果为0;

3、递归实现阶乘
当n为1时f函数就已经终止了,我们从n ,n-1 , n-2 …2 ,1依次求得阶乘来求得n的阶乘
#include <stdio.h>

long f(long n)
{
if(1 == n)
{
return 1;
}
else
{//所求阶乘不是1时,反复调用自己来逐个求得每个数的阶乘
return f(n-1) *n;
}
}

int main(void)
{
printf("%d",f(2));
}
4、阶乘实现递归改良代码:
#include <stdio.h>

long f(long n)
{
if(1 == n)
{
return 1;
}
else
{//所求阶乘不是1时,反复调用自己来逐个求得每个数的阶乘
long all;
all = f(n-1) *n;
return all;
}
}

int main(void)
{
int val;
long result;
printf(“请输入求阶乘的数:\n”);
scanf("%d",&val);
result = f(val);
printf(“阶乘结果:%d\n”,result);
}

图示:

5、递归实现1+2+3+4+…+1000程序
代码:
#include <stdio.h>

long sum(int n)
{
if(1 == n)
{
return 1;
}
else
{
return sum(n-1)+n;
}
}

int main(void)
{
long result;
result = sum(5);
printf("%ld",result);//输出结果是:15
}
课时 53、P53 _ 递归4 _ 布置作业_汉诺塔
1、说明

课时 54、P54 _ 递归5 _ 一个函数为什么可以自己调用自己_1
1、定义:一个函数直接或者间接的调用自己
2、直接调用和间接调用自己
直接调用:

间接调用:

2、为什么一个函数可以自己调用自己呢?

理解:
1)、当函数在调用自己或者在调用其他函数时所执行的步骤都是:传入实参和返回地址等信息、分配内存空间、转移控制权;所以对于计算机来说只要是调用函数执行的步骤都是一样的;所以函数在计算机看来是可以自己调用自己的;
2)、先调用后返回:最后调用的函数最先返回值,最后调用的函数最后返回值;无论调用的是什么函数,在计算机看来函数执行完毕所进行的步骤都是:存储返回值、释放被调函数空间、依照被调函数保存的返回地址将控制权转移到调用函数;
代码:
#include <stdio.h>

int get(int n)
{
if(n < 3)
{
printf(“HAHHAHA\n”);
}
else
{
n = get(n-1);//直接自己调用自己
}
return n;
}

int main(void)
{
int result;
result = get(5);
printf(“result = %d”,result);
}
输出结果:

课时 55、P55 _ 递归6 _ 递归必须满足三个条件_1
1、递归必须得有一个明确的终止条件;
2、该函数所处理的数据规模必须在递减;(递归的值可以递增)
3、这个转化必须是可解的;
课时 56、P56 _ 递归7 _ 循环和递归的比较_1
1、递归
优点:易于理解
缺点:速度慢、存储空间大
2、循环
优点:速度快、存储空间小
缺点:不易理解
课时 57、P57 _ 递归8 _ 汉诺塔_1
1、汉诺塔实现的伪算法

2、代码:
#include <stdio.h>

void get(int n,char A,char B,char C)
{
/*
如果盘子的数量为1
将A柱子上的盘子直接移到C柱子上
否则
将A柱子上的n-1个盘子借助C移到B上;
将A柱子上剩余的一个盘子移到C柱子上;
B柱子上的n-1个盘子借助A柱子移到C柱子上;
*/
if(1 == n)
{
printf(“将编号为%d的盘子从%c上直接移动到%c上\n”, n, A, C);
}
else
{
get(n-1, A, C, B);
printf(“将编号为%d的盘子从%c上直接移动到%c上\n”, n, A, C);
get(n-1, B, A, C);
}
}

int main(void)
{
char ch1 = ‘A’;
char ch2 = ‘B’;
char ch3 = ‘C’;
int val;
printf(“请输入需要移动的盘子的个数:”);
scanf("%d",&val);
get(val,‘A’,‘B’,‘C’);
}

注意:在函数中A,B,C代表队不一定就是A,B,C,在移动盘子的时候盘子的位置会变化,所以移动的柱子也是不同的,从哪个柱子转移到哪个柱子也是在不停变化的;
图示:

课时 58、P58 _ 递归9 _ 递归的应用_1
1、树和森林就是以递归的方式定义的;
树和图的很多算法都是以递归来实现的;
很多数学公式就是以递归的方式定义的;
课时 59、线性结构总复习 2线性结构和非线性结构关系 3栈队列链表数组之间的关系【重点】_1
1、逻辑结构
线性
链表、数组
栈(内部是链表)和队列(内部是数组)是线性结构的具体应用,是一种特殊的线性结构;
栈和队列是线性结构的具体的代表,而非实质;
非线性
树、图
2、物理结构
课时 60、P60_树1_树的定义_1
1、树的定义
专业定义:有且只有一个称为根的节点;
有若干个互不相交的子树,这些子树本身也是一棵树;
通俗定义:树是由节点和边组成(边就是指针);
每一个节点只有一个父节点,但可以有多个子节点;
但有一个节点例外,,该节点没有父节点,即为根节点;

2、树的分类
3、树的操作
4、树的应用
课时 61、P61_树2_树的专业术语解释_1
1、专业术语
节点 父节点 子节点 子孙(相隔至少一层) 兄弟(同一层节点的关系)
深度:从根节点到最底层节点的层数;根节点是第一层;
叶子节点:没有子节点的节点;
非终端节点:实际就是非叶子节点(有子节点的节点);
度:子节点的个数称为度(一个节点的度由子节点的个数决定,树的度由子元素最多的节点决 定,即子节点最多的哪个元素的度就是树的度);
课时 62、P62_树3_树的分_1
1、分类
一般树:任意一个节点的子节点个数都不受限制;(一般树也可以分为有序树和无序树)
二叉树:任意一个节点的子节点的个数最多是两个,并且子节点的位置不可更改;
分类:
一般二叉树
满二叉树:
在不增加树的层数的情况下,无法再增加一个节点二叉树就是满二叉树;

完全二叉树:
如果只是删除了满二叉树最底层最右边连续若干个节点,这样形成的二叉树就是完全二叉树;

满二叉树是完全二叉树的一个特例;
二叉树图示:

森林:n个互不相交的树的集合;
树的存储:
注意:内存是线性一维的,树并不是一维的,所以需要考虑存储的问题;
课时 63、P63_树4_二叉树连续存【重点】_1
1、二叉树的存储
1)、连续存储(完全二叉树)(要连续存储二叉树则必须要先将二叉树转化为完全二叉树)
(只有完全二叉树可以连续存储)
注意:二叉树的存储不可以只存储有效数据,因为他并不是像线性结构一样是固定前后关系的,而我们的内存也是线性的,所以我们不能只存放有效的数据节点,只存放有效是数据节点并不能换原树的原来的结构,所以我们需要补足为完全二叉树之后再存储。存放一部分无效节点数据;
二叉树:非线性 内存、数组、链表:线性
使用完全二叉树存储的优点:
根据节点的个数可以推算出树的层数(因为每个只有两个分支);
可以找到完全二叉树中任一个节点的父节点和子节点(也包括判断是否存在子节点),且速度快;
完全二叉树的缺点:
耗费内存空间大(因为在连续存储完全二叉树时需要存放一些空节点);

2)、链式存储
2、一般树的存储
3、森林的存储
课时 64、P64_树5_二叉树的链式存储_1
1、链式存储
与我们的指针相似,分为指针域和数据域;
但是在存放完全二叉树的时候,链式存储分为三个区域,分别是:左指针域,数据域,右指针域
左指针域存放左子树的地址,右指针域存放右子树的地址;
这种存储方式节约了很多空间,不必存放很多空闲的数据节点,并且也可以找到节点的子节点,但是无法找到节点的父节点;
链式存储的空间并不是连续的;
图示:

课时 65、P65_树6_普通树的存储_1
1、一般树的存储
双亲表示法(易于求父节点)
每一个元素节点存储在内存中都保存了它的父节点的地址(这样就可以得出树的结构)
此种存储方式不要求二叉树中的节点一定是按照顺序存储的;
图示:

孩子表示法(易于求子节点)
一部分存放节点,一部分是指针域,将父节点指向它的子节点(多个子节点时依次指向)
图示:

双亲孩子表示法(求父节点和子节点都和方便)
分为三个部分:节点数据,父节点地址,指针域指向子节点;
图示:

二叉树表示法
把一个普通树转换为二叉树来存储;
具体的转换方法:
保证任意一个节点的左指针域指向它的第一个孩子,右指针域指向它下一个兄弟节点;
只要能满足此条件就可以将一个普通的树转换为二叉树;
一个普通的树转换为二叉树是一定没有右子树的;
图示:

课时 66、P66_树7_森林的存储
1、森林:多个树的组合
2、先将森林转换为二叉树
将旁边的树当作是树的兄弟节点,与一般树的转换方法一样,左指针域指向它的第一个孩子,右指针域指向它下一个兄弟节点;
图示:

课时 67、P67_树8_二叉树的先序遍历(常考)
1、树的常用操作(一般指的是二叉树的操作)
1)、树的遍历;
先序遍历(先访问根节点)
先访问根节点;
再先序访问左子树;
再先序访问右子树;
举例:

中序遍历
后序遍历
2)、已知两种遍历求原始二叉树;
课时 68、P68_树9_二叉树的中序遍历
1、中序遍历(中间访问根节点)
先中序遍历左子树;
再访问根节点;
再中序遍历右子树;
举例:

课时 69、P69_树10_二叉树的后序遍历
1、后序遍历(最后访问根节点)
先后序遍历左子树;
再后序遍历右子树;
再访问根节点;
举例:

课时 70、P70_树11_已知两种遍历序列求原始二叉树概述_1
1、任意知道树的一种遍历结果(无论是先序,中序还是后序)都不能推出树的原始的结构;
2、但是我们如果知道 先序和中序 或者 中序和后序 的结果,可以推出原始二叉树的结构;
3、但是如果已知先序和后序是无法推出二叉树的原始结构;

课时 71、P71_树12_已知先序和中序求后序_1
1、先序:先访问根节点 中序:中间访问是根节点 后序:最后访问根节点
举例:

自己举例的方法:
画树,求出先序,中序、后序的结果,依次根据两两来推出图和第三者;
课时 72、P72_树13_已知中序和后序求先序_1
1、举例:

课时 73、P73_树14_树的应用简单介绍_1
1、树是数据库中数据组织的一种重要形式;
2、操作系统子父进程的关系本身就是一棵树;
3、面向对象语言中类的继承关系本身也是一棵树;
4、赫夫曼数
课时 74、P74_树15_复习上节课知识
1、举例:

2、举例:

3、举例:

课时 75、P75_树16_链式二叉树遍历具体程序演示
1、二叉树一般是动态创建,动态创建的话需要用到递归,以下的程序静态创建二叉树;
2、利用递归的思想不断的去调用左子树、左子树的左子树、左子树的左子树的左子树;然后再以同理输出根节点和右子树节点;
3、在进行值的判断的时候我们最好将语句返过来写,例如:if(NULL != pT1->pLchild)
所创建的二叉树图示:

代码:
#include <stdio.h>
#include <malloc.h>

struct BTNode//定义节点的类型
{
int data;//数据域
struct BTNode * pLchild;//左指针域 代表指向左子节点
struct BTNode * pRchild;//右指针域 代表指向右子节点
} ;//分号不可少

//前置声明
struct BTNode * CreateBTree(void);
void PreTraverseBTree(struct BTNode * pT1);
void InTraverseBTree(struct BTNode * pT1);
void PostTraverseBTree(struct BTNode * pT1);

int main(void)
{
//通过CreatBTree动态来创建树。并返回根节点的地址 与链表相似
struct BTNode * pT = CreateBTree();

//输出二叉树
//先序
printf("先序遍历结果:");
PreTraverseBTree(pT);
printf("\n");//中序
printf("中序遍历结果:");
InTraverseBTree(pT);
printf("\n");//后序
printf("后序遍历结果:");
PostTraverseBTree(pT);
printf("\n");

}

//创建二叉树
struct BTNode * CreateBTree(void)//struct NTNode *是返回值类型
{
//创建二叉树的各个节点 ,并用不同指针指向
struct BTNode * pA = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pB = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pC = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pD = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pE = (struct BTNode *)malloc(sizeof(struct BTNode));

//节点数据域放入数值
pA->data = 'A';
pB->data = 'B';
pC->data = 'C';
pD->data = 'D';
pE->data = 'E';//节点指针域指向
pA->pLchild = pB;
pA->pRchild = pC;
pB->pLchild = NULL;
pB->pRchild = NULL;
pC->pLchild = pD;
pC->pRchild = NULL;
pD->pLchild = NULL;
pD->pRchild = pE;
pE->pLchild = NULL;
pE->pRchild = NULL;//返回根节点地址
return pA;

}

//先序遍历 先访问根,再先序访问左子树 ,再先序访问右子树
void PreTraverseBTree(struct BTNode * pT1)
{
//当前的根节点不为空时才执行
if(NULL != pT1)
{
//输出当前节点的数据 先遍历根节点
printf("%c ",pT1->data);
//左子树 不为空时才继续遍历 再遍历左子树
if(NULL != pT1->pLchild)
{
PreTraverseBTree(pT1->pLchild);
}
//右子树 不为空时才继续遍历 再遍历右子树
if(NULL != pT1->pRchild)
{
//pT1->pLchild //代表的就是以当前节点为根节点的整个左子树
PreTraverseBTree(pT1->pRchild);
}
}
}

//中序遍历 先访问左子树,再中序访问根 ,再先中序访问右子树
void InTraverseBTree(struct BTNode * pT1)
{
//当前的根节点不为空时才执行
if(NULL != pT1)
{
//左子树 不为空时才继续遍历 遍历左子树
if(NULL != pT1->pLchild)
{
InTraverseBTree(pT1->pLchild);
}

 //输出当前节点的数据          遍历根节点 printf("%c      ",pT1->data);//右子树 不为空时才继续遍历            遍历右子树 if(NULL != pT1->pRchild){//pT1->pLchild //代表的就是以当前节点为根节点的整个左子树InTraverseBTree(pT1->pRchild);}
}

}

//后序遍历 先访问左子树,再先后序访问右子树 , 再后序访问根节点
void PostTraverseBTree(struct BTNode * pT1)
{
//当前的根节点不为空时才执行
if(NULL != pT1)
{
//左子树 不为空时才继续遍历 遍历左子树
if(NULL != pT1->pLchild)
{
PostTraverseBTree(pT1->pLchild);
}

 //右子树 不为空时才继续遍历            遍历右子树 if(NULL != pT1->pRchild){//pT1->pLchild //代表的就是以当前节点为根节点的整个左子树PostTraverseBTree(pT1->pRchild);} //输出当前节点的数据          遍历根节点 printf("%c      ",pT1->data);}

}

结果图示:

注意:分清楚每一种遍历方式的特点,再使用递归实现!
课时 76、P76_树17_5种常用排序概述 和 快速排序详细讲解

1、排序和查找的关系
排序是查找的前提;排序是重点;
先排序再查找;
2、快速排序(递归思想)
快速排序思路:
找出第一个元素在排序后的位置,且确定该元素的位置后,比该元素小的在前面,比该元素大的在后面,然后,利用递归思想用同样的方式逐个排序该元素前面和后面的元素,依次确定位置,每次只能确定一个元素的位置;
如何确定元素位置?
用指针Low和High分别指向第一个和最后一个元素;
将第一个元素的值(用Low指向)存放在一个其他变量val中;
当High指向的值(最后一个元素)与val相比较,如果High指向的值小于val,那么将High所指向的元素的值赋给Low所指向的元素(即High指向的值应该放在当前所确定的元素的位置的左边),若High所指向的值大于val,那么就将High左移;
此时,将Low指针后移一位,指向后一个元素,将移动后Low指向的元素与val值相比较,若val的值大于Low所指向的值,那么将Low指向的值赋值给High指向的值(表明当前Low指向的值应该放在val值的右边),若val的值小于Low所指向的值,Low继续往后移动指向下一个元素;
当High和Low指向同一个元素时,即表示当前的位置就是val排序后所处的位置。并且比val小的再val的左边,比val大的再val的右边,每进行一次排序只能确定一个元素的位置。

指向数组第一个元素和最后一个元素的low和high始终都没有改变,至少在FindPose函数中作为局部变量改变,用于赋值和移动元素,找出固定元素的位置!!!!!
代码:
#include <stdio.h>

//前置声明
void QuickSort(int * a, int low, int high);
int FindPose(int *a ,int low , int high);

int main(void)
{
//数组元素的空间是连续分配的,可以进行++或–移动空间
int a[6] = {20, -1, 0, 7, 4, 3};
int i,j;

printf("排序前的数组:\n");
for(i=0; i<6; i++)
{printf("%d    ",a[i]);
}
printf("\n");
//参数意义:传入需要排序的数组,   第一个元素的下标,     最后一个元素的下标
QuickSort(a, 0, 5); printf("排序后的数组:\n");
for(j=0; j<6; j++)
{printf("%d    ",a[j]);
}
printf("\n");

}
//要跨函数操作必须要用指针
void QuickSort(int * a, int low, int high)
{
int pos;//存储当low当high相等时的位置,用于隔断前后分别排序

//只有当第一个排序元素的下标 小于 最后一个需要排序的元素下标 才进行排序操作
if(low < high)
{pos = FindPose(a, low, high);//找出每次需要排序的切断点(即low和high相等的位置) //low和high在此函数中指向的已知都是第一个和最后一个元素,而pos是固定元素的位置 //排序第一个固定元素之前的部分 QuickSort(a, low, pos-1); //排序第一个固定元素之后的部分 QuickSort(a, pos+1, high);
}

}

//寻找当前需要查找元素的固定位置
int FindPose(int *a ,int low , int high)
{
//保存第一个元素的数值
int val = a[low];
//当low和high不相等时,才需要进行移动以及赋值 ,表示当前需要确定的元素位置未确定
while(low < high)
{
//当high的值大于val,应该放在val右边,不赋值,high向左移动
while (low<high && a[high]>=val)
{
–high;
}
//当high的值小于val,应该放在val左边,将high的值赋值给low
a[low] = a[high];

 //当low的值小于val,应该放在val左边,不赋值,low向右移动 while (low<high && a[low]<=val){++low;} //当low的值大于val,应该放在val右边,将low的值赋值给high a[high] = a[low];
}   //赋值交换完毕,high和low指向相同,即为第一个元素的固定位置
a[low] = val;
return  low;//或return  high,  返回的是下标而不是下标对应的值

}

课时 77、P77_树18_再次讨论什么是数据结构
1、数据结构是研究数据存储和数据的操作的一门学问;
数据的存储分为两部分:个体的存储、个体关系的存储;
从某个角度而言,数据的存储最核心的是个体关系的存储,个体的存储可以忽略不记;
课时 78、P78_树19_再次讨论到底什么是泛型
1、泛型:
同一种逻辑结构,无论该逻辑结构的物理存储是什么样子的,我们都可以对他执行相同的操作;
举例:
例如:数组和链表都是同一种逻辑结构(线性结构),他们具体的存储数据的方式是不同的,单位他们某些功能的实现在一定程度上相似的,我们可以通过一定的程序代码来让用户觉得对这两种存储方式的操作是相同的,即这就是泛型;

数据结构学习笔记-郝斌相关推荐

  1. Java入门学习笔记——郝斌(一)概述及面向对象

    Java入门学习笔记--郝斌 1.Java概述 java的起源和发展 java的特点 java的应用领域 java学习目标 环境变量的设置 为什么要设置path? path的设置 有关classpat ...

  2. 数据结构学习笔记(七):哈希表(Hash Table)

    目录 1 哈希表的含义与结构特点 1.1 哈希(Hash)即无序 1.2 从数组看哈希表的结构特点 2 哈希函数(Hash Function)与哈希冲突(Hash Collision) 2.1 哈希函 ...

  3. 数据结构学习笔记(六):二叉树(Binary Tree)

    目录 1 背景知识:树(Tree) 2 何为二叉树(Binray Tree) 2.1 二叉树的概念与结构 2.2 满二叉树与完全二叉树 2.3 二叉树的三种遍历方式 3 二叉树及其遍历的简单实现(Ja ...

  4. 数据结构学习笔记(五):重识字符串(String)

    目录 1 字符串与数组的关系 1.1 字符串与数组的联系 1.2 字符串与数组的区别 2 实现字符串的链式存储(Java) 3 子串查找的简单实现 1 字符串与数组的关系 1.1 字符串与数组的联系 ...

  5. 数据结构学习笔记(四):重识数组(Array)

    目录 1 数组通过索引访问元素的原理 1.1 内存空间的连续性 1.2 数据类型的同一性 2 数组与链表增删查操作特性的对比 2.1 数组与链表的共性与差异 2.2 数组与链表增删查特性差异的原理 3 ...

  6. 数据结构学习笔记——顺序表的基本操作(超详细最终版+++)建议反复看看ヾ(≧▽≦*)o

    目录 前言 一.顺序表的定义 二.顺序表的初始化 三.顺序表的建立 四.顺序表的输出 五.顺序表的逆序输出 六.顺序表的插入操作 七.顺序表的删除操作 八.顺序表的按位和按值查找 基本操作的完整代码 ...

  7. Python数据结构学习笔记——链表:无序链表和有序链表

    目录 一.链表 二.无序链表 实现步骤分析 三.无序链表的Python实现代码 四.有序链表 实现步骤分析 五.有序链表的Python实现代码 结语 一.链表 链表中每一个元素都由为两部分构成:一是该 ...

  8. Python数据结构学习笔记——队列和双端队列

    目录 一.队列的定义 二.队列 实现步骤分析 三.队列的Python实现代码 四.队列的应用 六人传土豆游戏 五.双端队列的定义 六.双端队列 实现步骤分析 七.双端队列的Python实现代码 八.双 ...

  9. Python数据结构学习笔记——栈

    目录 一.栈的定义和特性 (一)栈的定义 (二)栈的反转特性 二.实现分析步骤 三.栈的Python实现代码 四.栈的应用 (一)匹配圆括号 (二)匹配符号 (三)模2除法(十进制转二进制) (四)进 ...

最新文章

  1. 赚到!4个Python初学者必学的Numpy小技巧
  2. 4.Azure创建点到站点的***隧道(下)
  3. Python List:合并多个list,listd的合并
  4. 最好电脑操作系统_操作系统都有哪些呢
  5. MongoDB常用操作命令大全
  6. sql中根据逗号分隔,查出多行数据
  7. 数值分析(8)-最佳一致逼近多项式
  8. android 停止服务执行,android - 为什么在停止服务(执行onDestroy已执行)后,服务中的变量没有“重置”?...
  9. 改变自己,永不会晚!
  10. c语言编译defined,#if defined(__GNUC__)的意思是不是如果使用的是GCC编译器?
  11. 2015年全部企业校园招聘情况+薪资水平!
  12. 100层楼2个鸡蛋,测试其最低破碎楼层问题
  13. duilib设置透明窗口_使用duilib开发半透明异形窗体程序(补充)
  14. 苹果开发者公司账号的申请步骤
  15. 阿里8年测试老鸟教你软件测试工程师简历,技术栈,项目经验怎么写
  16. python的 networkx画网络结构图,节点大小能够随权重变化
  17. JDWP exit error AGENT_ERROR_TRANSPORT_INIT(197): No transports initialize
  18. 应届大学毕业生面试应答
  19. 【论文翻译】(第二部分)The Fourier decomposition method for nonlinear and non-stationary time series analysis
  20. 2021计算机系统大作业 CSAPPHello‘s P2P

热门文章

  1. htc hd2 android6,古董神机HTC HD2刷上安卓6.0!运行还挺流畅_游侠网
  2. 中龍鸿业安全稳定必学的盘口言语有哪些?什么是盘口言语?
  3. UIScrollView 平滑划动处理
  4. DDR2 PCB设计规则
  5. 波段融合生成全色影像
  6. html写签到程序,如果写自动签到程序的模板
  7. 全球 IT 富豪榜!
  8. springboot+Mybatis+MySql 一个update标签中执行多条update sql语句
  9. 北斗通信模块 北斗gps模块 北斗通信终端DTU
  10. 计算机教学设计模板语文,计算机模板教学设计.doc