文章目录
前言:
这篇是学习笔记:记录学完C语言全部课程后,学习用C代码的方式实现初级数据结构。
笔记简介:这篇笔记写于2022/07/02,和上一篇《学习笔记:C语言从初阶到进阶学习过程中的要点疑点和难点》同时开始写。不同的是,上一篇C语言学习笔记靠回忆慢慢写,本篇初级数据结构是边学边写!
一、数据结构前言:
还没想好怎么写,占个沙发。
二、时间复杂度空间复杂度:
算法效率:
概念的东西,原文照抄。
算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率被称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间,在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
时间复杂度:
时间复杂度的定义:
在计算机科学中,算法的时间复杂度是一个(数学)函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。
计算时间复杂度:
计算时间复杂度对数学知识要求挺高,起码对高中学过的知识还能不忘,需要根据不同类型的算法,算出对应的数学(函数)公式。实际我们计算的时间复杂度都是估算值,在可选的算法中,通过对时间复杂度的大致判断,选择执行效率最高的算法。
大O的渐进表示法:
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
// 请计算一下Func1基本操作执行了多少次?
void Func1(int N) {
int count = 0;
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
++count;
}
}
for (int k = 0; k < 2 * N; ++k) {
++count;
}
int M = 10;
while (M--) {
++count;
}
printf("%d\n", count);
}
Func1 执行的基本操作次数 :
N = 10-> F(N) = 130
N = 100 ->F(N) = 10210
N = 1000 ->F(N) = 1002010
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
使用大O的渐进表示法以后,Func1的时间复杂度为:
N = 10 ->F(N) = 100
N = 100 -> F(N) = 10000
N = 1000 -> F(N) = 1000000
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)
试题01:一个整型数组除了两个数字之外,其他数字都出现两次,找出这两个只出现一次的数字。要求:时间复杂度是O(n),空间复杂度是O(1)。
//一个整型数组除了两个数字之外,其他数字都出现两次,找出这两个只出现一次的数字。
//要求:时间复杂度是O(n),空间复杂度是O(1)。
int main(){
int num[] = {1,2,3,4,3,5,2,1};
int sz = sizeof(num)/sizeof(num[0]);//计算数组的元素个数
int x = 0, y = 0;//假设x,y就是那两个数
int ret = 0;
int i = 0;
int j = 0;
for(i=0;i<sz;i++){
ret ^= num[i]; //计算所有数字的按位异或的结果
}
//找结果ret里面为1的位
for(i=0;i<31;i++){
/*
如果ret是0110,怎么就能根据下面的逻辑判断得出ret的第i位就是1?
现在的我看到当初学习的时候问的这个问题,容许我骂自己是大傻x。
(产生这个疑惑的缘由就是我把1的二进制当成了-1(11111111.....))
*/
if(ret & (1<<i)){ //if((ret>>j) & 1)
break;
}
}
/*
将元数据分为两组,第i位为1的分为a组,为0的分为b组
x和y一定会分别进入a和b组,然后分别把a和b组按位异或得到的值分别就是x和y
*/
for(j=0;j<sz;j++){
if(num[j] & (1<<i)){ //a组
x ^= num[j];
}else{ //b组
y ^= num[j];
}
}
printf("x = %d ant y = %d\n", x, y);
return 0;
}
空间复杂度:
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。
随着计算机的发展,存储介质空间越来越大,空间复杂度慢慢的变得不重要了!
三、顺序表_链表:
线性表(Linear list):
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表(Sequence list):
顺序表本质就是数组,动态增长,并且要求里面储存的数据必须是从左往右连续的。
顺序表的缺陷:
①动态增容有性能消耗
②需要头部插入数据,需要挪动数据
链表(Linked list):
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
-
单向、双向
-
带头、不带头
-
循环、非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
-
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结
构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。 -
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向
循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而
简单了,后面我们代码实现了就知道了。