郝斌老师讲的《数据结构与算法》课程通俗易懂,在b站人气很高!视频链接为:
https://b23.tv/med0e3
若本笔记有看不懂的地方,请去看上面链接的视频,很方便地检索到你想到看的知识点。
以下为我的数据结构与算法总结
目录
一、数据结构概述
1.1数据结构定义
学完数据结构之后会对面向过程的函数有一个更深的了解。
我们如何把现实中大量而复杂的问题以特定的数据类型和特定的存储结构(个体的关系) 保存到主存储器(内存)中,以及在此基础上为实现某个功能(比如查找某个元素,删除某个元素,对所有元素进行排序)而执行的相应操作,这个相应的操作也叫算法。
(比如班里有15个人,其信息量也许一个数组就搞定了,但是假如10000个,怎么办?内存也许没有这么多连续的空间,所以我们改用链表,you see这就是与存储有关系。又比如,人事管理系统的信息存储, 因为存在着上下级的关系,所以数组和链表就无能为力了,这时候我们用树,再比如我们做的是交通图,站和站之间肯定要连通,这 时候以上的存储方式又无能为力了,所以我们又有了图。图就是每个结点都可以和其他结点产生联系。所以当我们要解决 问题时,首先要解决的是如何把这些问题转换成数据,先保存到我们的主存中,)
数据结构 = 个体的存储 + 个体的关系存储
算法 = 对存储数据的操作
算法定义:通俗的说算法是解题的方法和步骤
衡量算法的标准
时间复杂度:程序大概要执行的次数,而非执行的时间;
空间复杂度:程序执行过程中大概所占用的最大内存空间;
难易程度:用易懂,避免过于复杂;
健壮性。
数据结构的地位:数据结构是软件中最核心的课程
程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言
二、预备知识(C语言的复习)
2.1 指针
指针的重要性:(内存是可以被CPU直接访问的,硬盘不行;
主要靠地址总线,数据总线,控制总线。)
指针是C语言的灵魂。
地址:内存单元的编号,从0开始的非负整数。范围0 ~ FFFFFFFF(即0 ~ 4G-1)
指针就是地址,地址就是指针;
指针变量是存放内存单元地址的变量;
指针的本质是一个操作受限的非负整数。
指针的分类:
①基本类型的指针;
②指针和数组的关系
指针的具体知识这里不再赘述。想要复习相关知识请去看郝斌老师的笔记:https://blog.csdn.net/weixin_45519751/article/details/107869403
2.2 结构体
为什么会出现结构体
为了表示一些复杂的数据,而普通的基本类型变量无法满足要求
什么叫结构体
结构体是用户根据实际需要自己定义的复合数据类型
如何使用结构体
两种方式:
struct Student st = {
1000, "zhangsan", 20}
struct Student * pst = &st;
方式1.
st.sid
方式2.
pst->sid
pst所指向的结构体变量中的sid这个成员
我们一般使用方式2,因为指针在参数传递过程中占用的内存少(4个字节),程序运行效率高。
注意事项:
结构体变量不能加减乘除,但可以相互赋值;
普通结构体变量和结构体指针变量作为函数参数的传递(结构体指针变量传参效率更高);
结构体知识请去看郝斌老师的笔记:
https://blog.csdn.net/weixin_45519751/article/details/107869403
2.3 动态内存的分配和释放(malloc())
(动态分配的内存一定要手动释放,否则造成内存泄露。)
想要复习相关知识请去看郝斌老师的笔记:https://blog.csdn.net/weixin_45519751/article/details/107869403
2.4 关于指针、结构体、动态内存、typedef的例子
例子1:
# include <stdio.h>
# include <malloc.h>
int main(void)
{
int a[5] = {
4, 10, 2, 8, 6};
int len;
printf("请输入你需要分配的数组的长度: len = ");
scanf("%d", &len);
int * pArr = (int *)malloc(sizeof(int) * len);
// *pArr = 4; //类似于 a[0] = 4;
// pArr[1] = 10; //类似于a[1] = 10;
// printf("%d %d\n", *pArr, pArr[1]);
//我们可以把pArr当做一个普通数组来使用
for (int i=0; i<len; ++i)
scanf("%d", &pArr[i]);
for (i=0; i<len; ++i)
printf("%d\n", *(pArr+i));
free(pArr); //把pArr所代表的动态分配的20个字节的内存释放
return 0;
}
例子2:跨函数使用内存
# include <stdio.h>
# include <malloc.h>
struct Student
{
int sid;
int age;
};
struct Student * CreateStudent(void);
void ShowStudent(struct Student *);
int main(void)
{
struct Student * ps;
ps = CreateStudent();
ShowStudent(ps);
return 0;
}
void ShowStudent(struct Student * pst)
{
printf("%d %d\n", pst->sid, pst->age);
}
struct Student * CreateStudent(void)
{
struct Student * p = (struct Student *)malloc(sizeof(struct Student));
p->sid = 99;
p->age = 88;
return p;
}
例子3:typedef函数的使用
typedef int INT; // 相当于给int起了一个别名 INT
typedef struct Student
{
int sid;
char name[100];
char sex;
} ST; //ST st 就相当于 struct Student st,给struct Student 起了别名ST,这样简洁了代码
typedef struct Student
{
int sid;
char name[100];
char sex;
} * ST; //ST就相当于struct Student *
若不懂typedef则请去看郝斌老师的笔记:
https://blog.csdn.net/weixin_45519751/article/details/107883393
三、线性结构
线性结构即把所有节点用一条直线串起来。
3.1 连续存储【数组】
什么叫数组:元素类型相同,大小相等。
数组的优缺点
优点:存取速度很快
缺点:插入删除元素很慢(因为,你要是在数组中间插入一个值的话,需要把此值要插入到位置的后面的所有元素向后移。显然,这样导致速度很慢)
数组例子:
我们想自己实现数组的功能,即我们写一个结构体,里面有数组首地址、当前数组内已有元素的长度(有效长度)、数组长度,然后编写一些函数实现对数组的操作,分别为:初始化数组、获取某个位置上的元素、判断数组是否已经满了、判断数组是否为空、打印数组内所有元素、向数组中追加元素、向数组某个位置插入元素、排序(本例用的是选择排序)。
# include <stdio.h>
# include <malloc.h> //包含了malloc函数
# include <stdlib.h> //包含了exit函数
//定义了一个数据类型,该数据类型的名字叫做struct Arr, 该数据类型含有三个成员,分别是pBase, len, cnt
struct Arr
{
int * pBase; //存储的是数组第一个元素的地址
int len; //数组所能容纳的最大元素的个数(数组有效长度)
int cnt; //当前数组有效元素的个数
};
//初始化数组
void init_arr(struct Arr * pArr,int length);//分号不能省
//获取某个位置上的元素
int get(int pos);
//判断数组是否已经满了
bool is_full(struct Arr * pArr);
//判断数组是否为空
bool is_empty(struct Arr * pArr);
//打印数组内所有元素
void show_arr(struct Arr * pArr);
//向数组中追加元素
void append_arr(struct Arr * pArr,int val); // pos的值从1开始
//向数组某个位置插入元素
void insert_arr(struct Arr * pArr,int pos,int val);
//排序(本例用的是选择排序)
void sort_arr (struct Arr * pArr);
//倒置数组
void inversion_arr(struct Arr * pArr);
int main(void)
{
struct Arr arr;
int val;
init_arr(&arr, 6);
show_arr(&arr);
append_arr(&arr, 1);
append_arr(&arr, 10);
append_arr(&arr, -3);
append_arr(&arr, 6);
append_arr(&arr, 88);
append_arr(&arr, 11);
if ( delete_arr(&arr, 4, &val) )
{
printf("删除成功!\n");
printf("您删除的元素是: %d\n", val);
}
else
{
printf("删除失败!\n");
}
/* append_arr(&arr, 2);
append_arr(&arr, 3);
append_arr(&arr, 4);
append_arr(&arr, 5);
insert_arr(&arr, -1, 99);
append_arr(&arr, 6);
append_arr(&arr, 7);
if ( append_arr(&arr, 8) )
{
printf("追加成功\n");
}
else
{
printf("追加失败!\n");
}
*/
show_arr(&arr);
inversion_arr(&arr);
printf("倒置之后的数组内容是:\n");
show_arr(&arr);
sort_arr(&arr);
show_arr(&arr);
//printf("%d\n", arr.len);
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;
}
return;
}
bool is_empty(struct Arr * pArr)
{
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; i<pArr->cnt; ++i)
printf("%d ", pArr->pBase[i]); //int *
printf("\n");
}
}
bool append_arr(struct Arr * pArr, int val)
{
//满是返回false
if ( is_full(pArr) )
return false;
//不满时追加
pArr->pBase[pArr->cnt] = val;
(pArr->cnt)++;
return true;
}
bool insert_arr(struct Arr * pArr, int pos, int val)
{
int i;
if (is_full(pArr))
return false;
if (pos