文章内容目前还没有做完,之后会按照知识准备,线性表,树,堆这个顺序补充章节(相关的算法内容目前不作补充,先完善数据结构部分)。
重点内容我会用带括号的标题演示,加粗字体则需要记忆,代码只给出一些关键代码,具体是怎么串联的还要靠自己去学。
这种笔记类的文章看个乐呵就差不多了,本来是不打算发出来的,因为里面的内容和课堂上的内容完全没法比,可能只占了70%甚至更低。但有的同学苦于找不到比较适合复习资料,并且也实在是读不下几百上千页的ppt,那咱也只能把这个残次品发出来给各位评鉴一下了。咱学计算机的最不缺的就是开源精神,如果这文章能帮到你的话那就再好不过了
1基础知识的准备
这里还是放上老师的原话:背书不能高分,理想成绩还是要靠花时间看书多实践。
1.1参考资料
除了本学期大家使用的黑皮书以外,算法四、算法导论、计算机程序设计艺术等书目也常常被提起这些都是很经典的算法书,此外,具体数学也是在后续学习中不可或缺的一本重要书籍。其次是博客网站上别人加工好的文章,这类资料的知识覆盖没有书籍全面,但是具有很强的针对性,且都符合人类的阅读习惯(不需要像视频那样拖动进度条)最后就是通过看视频学习了,由于我没有什么好推荐的就先跳过它了。
1.2什么是数据结构,啥又是算法
1.2.1数据结构
数据结构就是对特定问题设计好的代码工具,能帮助我们减少重复编码的实践,并且可以灵活组合去解决更多的计算机领域的问题。“为了合理地使用一台计算机,我们需要理解存在于数据内的结构关系,以及在一台计算机内表示和操作这样的结构的基本技术”。
我们在学习数据结构时需要完成增删查改这些基本功能(当然,课程本身也是要求了一些额外功能的)。
1.2.2算法
算法是用来解决某一类问题的一套解决策略,具有很强的规律性。算法具有有穷性,确定性以及有效性。
1.2.3复杂度分析
顾名思义,复杂度是用来表示一个程序的复杂程度的,我们以此评定一个算法的性能是否足够优良(复杂度是评价算法性能的重要指标)。
1.2.3.1时间复杂度
用于描述程序运行时间的函数之间建立的一种相对的级别,本质是描述相对增长率的不等式。
1.2.3.1.1时间复杂度的推算
在这里我们介绍一个常用方法(循环类算法):写出执行次数,追踪变量变化,找出两者之间的联系并用数学表达式表达;然后确定循环终止条件;联立两个等式求解估算时间复杂度。
(重点:估算时间复杂度的通用方法)
1. 明确输入规模 n
-
定义
n的具体含义(如:数组长度、图的顶点数、数值的位数等)。 -
示例:
-
排序算法:
n= 待排序元素个数。 -
快速幂算法:
n= 指数的值(注意不是位数)。
-
2. 定位核心操作
-
找到算法中执行次数最多的语句(通常是循环或递归调用)。
-
示例:
-
循环中的基本操作(如比较、赋值)。
-
递归函数中的自我调用。
-
3. 建立执行次数 T(n) 的数学模型
-
循环结构:分析循环变量的变化规律。
-
递归结构:写出递推关系式(如
T(n) = aT(n/b) + f(n))。 -
嵌套操作:乘法法则(嵌套循环)或加法法则(并列操作)。
4. 求解 T(n) 并化简
-
解方程(如求和、递推式展开、主定理)。
-
保留最高阶项,忽略低阶项和常数系数。
经典问题:最大子序列和 leecode53

2线性表(Linear List)
线性表,顾名思义:线性表 是数据结构中最基本、最常用的一种结构,用于存储一组具有线性关系的数据元素。它的特点是数据元素之间按顺序排列,每个元素最多只有一个前驱和一个后继(除首尾元素外)。
线性表主要包括数组(Array/List),链表(LinkedList)栈(Stack)以及队列(Queue)
C语言里我们最常见最常用的就是数组,就先从他开始讨论吧。
2.1数组
回忆数组的特点:数组的查询是非常方便的,只要我们知道目标的索引(index)是多少就能快速定位获取(因为数组的内存分配是紧密连续的)。 缺点也很明显,因为要确保最后的内存是连续的,所以大小在声明之后就不能再改变了,则有可能引起空间的不足或者是浪费。
除此之外还有二维数组或者其他的高维数组,这里,我们着重了解它们的存储方式就好了(本质上还是紧密连续的内存空间),还有锯齿数组这些扩展概念这里就不多做介绍了,反正以后会用到。
2.1.1动态数组/向量(Vector)的实现
为了弥补缺点,人们就设计出了动态数组,也被称为向量。向量能够重新分配自己的内存空间大小,那我们要怎么实现呢?
为了对数组实现动态的内存分配,我们需要用指针来操作;然后就是为了区分数组的实际长度以及它的容量,在结构体中应该要使用两个变量,也就是len和capacity。此外加上它自己需要存储的数据data,结构体就应该是:
struct ArrayList{
ElementType *data;
int len;
int capacity;
};
然后来考虑基础功能的实现(具体操作和数组一致,这里就直接放代码了)
当然,需要注意的是,我们在删除以及增加元素时,会导致数组中出现“空位”(尾部修改除外),在这种情况发生时,我们要对数据的索引进行调整(详见removeByIndex以及addItem)
List createList() {
List L = (PtrArrayList)malloc(sizeof(struct ArrayList));
if (L == NULL) {
printf("Out of memery, create List fail\n");
return NULL;
}
L->data = malloc(sizeof(ElementType) * DEFAULT_CAPACITY);
if (L->data == NULL) {
printf("Out of memery, create list array fail\n");
free(L);
return NULL;
}
memset(L->data, 0, DEFAULT_CAPACITY);
L->capacity = DEFAULT_CAPACITY;
L->len = 0;
return L;
}
int addItem(List list, ElementType item, int pos) {
int i;
if (list == NULL) {
printf("\nlist is null\n");
return ERROR;
}
else if (list->len == list->capacity) {
printf("\nlist is full!\n");
return ERROR;
}
else if (pos < 0 || pos >= list->capacity) {
printf("\npos out of range!\n");
return ERROR;
}
for (i = list->len - 1; i >= pos; i--) {
list->data[i + 1] = list->data[i];
}
list->data[pos] = item;
list->len++;
return OK;
}
int addItemTail(List list, ElementType item) {
return addItem(list, item, list->len);
}
int findItem(const List list, ElementType item) {
int i = 0;
if (list == NULL)
return ERROR;
// for(i = 0; i < list->len; i++){
// if(list->data[i] == item)
// return i;
// }
// return ERROR;
while (i < list->len && list->data[i] != item)
i++;
return i > (list->len - 1) ? ERROR : i;
}
int removeItem(List list, ElementType item) {
int i;
if (list == NULL) {
printf("\nlist is null\n");
return ERROR;
}
int pos = findItem(list, item);
if (pos != ERROR) {
for (i = pos; i < list->len - 1; i++)
list->data[i] = list->data[i + 1];
list->data[list->len - 1] = 0;
list->len--;
return pos;
}
return ERROR;
}
int removeByIndex(List list, int pos) {
int i;
if (list == NULL) {
printf("\nlist is null\n");
return ERROR;
}
else if (pos < 0 || pos >= list->len) {
printf("\npos out of range!\n");
return ERROR;
}
for (i = pos; i < list->len - 1; i++)
list->data[i] = list->data[i + 1];
list->data[list->len - 1] = 0;
list->len--;
return OK;
}
int setItem(List list, ElementType item, int pos) {
if (list == NULL) {
printf("\nlist is null\n");
return ERROR;
}
else if (pos < 0 || pos >= list->len) {
printf("\npos out of range!\n");
return ERROR;
}
list->data[pos] = item;
return OK;
}
ElementType getItem(List list, int pos) {
if (list == NULL) {
printf("\nlist is null\n");
return ERROR;
}
else if (pos < 0 || pos >= list->len) {
printf("\npos out of range!\n");
return ERROR;
}
return list->data[pos];
}
int isEmpty(List list) {
if (list == NULL)
return ERROR;
return (list->len == 0);
}
void printList(const List list) {
int i;
if (list != NULL) {
printf("\n[ ");
for (i = 0; i < list->len; i++)
printf("%d ", list->data[i]);
printf("]\n");
}
}
(重点:动态数组的resize)
之后就是考虑扩容与缩容(resize)的问题了,我们把数组视为一个集装箱(数据结构),里面呢又有很多紧密排序的盒子(按索引排列的内存空间),盒子当中又装有很多货物(元素)。思路也很简单,为了扩容或者缩容,我们直接拿来一个全新的,满足容量需求的集装箱把货物搬过去就好了。因此,动态数组resize的实质是:对数组内存的再分配
另一个办法就是直接使用自带的realloc重新分配内存空间(这里就不补充对应的知识了,这属于C语言基础)。
2.1.2动态数组的性能讨论
之前也说过了,数组的查找是非常方便的,通过代码实践我们也发现,数组在除尾部以外的地方插入删除元素是非常麻烦的,需要对修改位置之后的所有元素都进行挪动。时间复杂度达到O(N)
预告:枚举(模拟、二进制枚举、二分枚举)
双指针(数组反转、数组循环移动、滑动窗口、快慢指针)
2.2链表(LinkedList)
简介:链表相对于数组而言是一种松散而非连续(逻辑有关,物理无关)的数据结构,具体表现于链表由一个又一个节点(Node)组成,节点又散落在内存各处。
上一节我们已经讨论过了数组的内存布局、访问方式以及性能特点,不难发现数组在多增删的场景下的性能是比较低下的,为了弥补这一缺点,计算机科学家又创造出来了链表这个数据结构。
一个表总结差异:
(重点:链表 vs. 数组的核心区别)
| 特性 | 链表 | 数组(动态/静态) |
|---|---|---|
| 内存布局 | 非连续,动态分配 | 连续内存 |
| 访问方式 | 顺序访问(O(n)) | 随机访问(O(1)) |
| 插入/删除 | O(1)(已知位置) | O(n)(需移动元素) |
| 扩容方式 | 动态增删节点,无内存拷贝 | realloc 可能触发内存拷贝 |
| 缓存友好性 | 差(内存不连续,易缓存未命中) | 优(连续内存,缓存命中率高) |
| 内存开销 | 每个节点额外存储指针 | 仅存储数据 |
2.2.1链表的实现
在此之前我们再来进行一些讨论,虚拟头节点(也作哑元Dommy),这个结构能帮助我们获取操作链表的对象,并且在一些操作场合中是非常友好的。Head和Tail能够确保每个节点的前驱后继都不为空。
链表的节点:包括存储的数据,以及指向后继的指针
struct Node{
ElementType Element;
Position Next;
//如果是双链表则还有
//Position Prev;
};
链表的基本功能(自己填空)
List createList() {
Position L = (Position)______(sizeof(struct Node));
if (!L) {
printf("\n ERROR");
}
L->Next = ______;
return L;
}
int IsEmpty(List L) {
return ______ = NULL; // 注意:这里有错误需要修正
}
Position Find(ElementType X, List L) {
Position P = ______;
while (______ && ______) {
P = ______;
}
______ // 缺少返回语句
}
void Insert(ElementType X, List L, Position P) {
Position TmpCell = (Position)______(sizeof(struct Node));
if (L == NULL)
printf("\ninsert list failed, out of memery!\n");
TmpCell->Element = ______;
TmpCell->Next = ______;
______ = TmpCell;
}
void Delete(ElementType X, List L) {
Position Prev, TmpCell;
Prev = ______(X, L);
if (Prev != NULL) {
TmpCell = ______;
Prev->Next = ______;
______(TmpCell);
}
}
Position Last(List L) {
if (______(L))
return NULL;
Position P = ______;
while (______ && ______)
P = ______;
return ______;
}
void PrintList(List L) {
Position P = ______;
printf("\nDummyHead->");
while (______) {
printf("[%d]->", ______);
P = ______;
}
printf("NULL\n");
}
(代码不能给完整的,不然直接照抄就完事了)
之后是一些和链表有关的经典问题
预告:链表反转、链表中的环(快慢指针解决)、约瑟夫环、多项式、计算器、链表归并
1459

被折叠的 条评论
为什么被折叠?



