经过几周C语言的学习,大家对程序编码也有了一定的基础,从今天开始,我们将进入下一阶段的学习,也就是数据结构的学习。
1、概念
程序 == 数据结构 + 算法
数据结构:程序操作数据对象的结构
算法:程序操作数据对象的方法
2、程序的效率的衡量指标
时间复杂度:数据量增长与程序运行时间增长呈现的比例函数关系称为时间复杂度函数,简称时间复杂度
o(c)
o(logn)
o(n)
o(nlogn)
o(n^2)
o(n^3)
...
o(2^n)
空间复杂度:数据量增长与程序空间增长呈现的比例函数关系称为空间复杂度
3、数据结构
3.1 逻辑结构
线性结构:一对一关系
树形结构:一对多关系
图形结构:多对多关系
3.2 存储结构
顺序存储
优点:
访问元素方便
缺点:
插入、删除效率低
无法利用小空间
链式存储
优点:
插入、删除效率高
可以利用小空间
缺点:
访问元素不方便
增加额外空间的开销
索引存储
索引存储 是为原数据集合额外建立一张 "索引表"的存储方式 ------ 索引表中记录了数据的关键字 和该数据在原存储区的物理地址 / 位置指针,查找数据时,先在索引表中快速找到目标关键字对应的地址,再通过地址直接访问原数据,而非遍历整个原数据集合。
可以理解为:书籍的目录就是 "索引表",书籍正文是 "原数据集合",找某个知识点时,先查目录(索引表)得到页码(物理地址),再翻到对应页码(直接访问),而非逐页翻书。
散列存储(哈希表)
散列存储 是直接根据数据的关键字,通过一个预先定义的散列函数(哈希函数),计算出该数据的物理存储地址 ,并将数据直接存到该地址的存储方式;查找时,用相同的散列函数计算关键字,直接得到地址并访问数据,无需额外的索引表。
可以理解为:快递柜的取件码就是散列函数的计算结果,快递(数据)的关键字是快递单号,通过快递公司的散列函数计算出取件码(存储地址),快递直接放到对应柜子;取快递时,输入单号重新计算取件码,直接打开柜子,无需查任何目录。
4、数据结构内容掌握
4.1 顺序表
4.2 链式表
4.3 顺序栈
4.4 链式栈
4.5 顺序队列
4.5 链式队列
4.6 二叉树(完全二叉树、满二叉树)
4.7 哈希表
4.8 常见的排序和查找算法
5、顺序表
5.1 静态顺序表
用栈区 / 全局区的固定数组(如int arr[100]),容量固定不可变,仅适用于数据量确定的场景,实实际开发用得少。
5.2 动态顺序表
本质等同于数组,通过申请堆区空间存储数据,通过首地址 + 下标偏移完成所有空间的访问
头文件:
cpp
#ifndef __SEQLIST_H
#define __SEQLIST_H
typedef int DataType;
DataType *CreatSeqlist(int len);
void DestorySeqlist(DataType **pparr);
#endif
源文件:
cpp
#include <stdio.h>
#include <stdlib.h>
#include "seqlist.h"
DataType* CreatSeqlist (int len) {
DataType *pret = NULL;
pret = malloc(len * sizeof(DataType));
if(pret == NULL) {
perror("fail to malloc");
return NULL;
}
return pret;
}
void DestroySeqlist(DataType **pparr) {
free(*pparr);
*pparr = NULL;
return;
}
6、链表
6.1 单向链表
什么是单向链表,首先得清楚什么是链表,不同于数组,元素间是存储在连续的空间,而链表中的元素,也就是节点,每个节点都由一个指针指着,可以不是连续的,但是可以通过前一个节点对后一个节点的地址的存储,去进行访问遍历,所以对于单向链表,也就是由前到后,依次遍历,但是不能由后往前遍历的,就叫做单向链表。
构造结构体节点:
cpp
typedef struct ListNode {
DataType val;
struct ListNode *next;
}ListNode;
其中的DataType是数据类型的重命名:
typedef int DataType;
这样是为了方便实现代码的复用,一行代码就可对数据类型进行切换。
然后对于单链表来说,又有无头链表,和有头链表,无头链表在遍历,查找,插入等操作都比较麻烦,所以我们先看看有头链表的实现**:**
(1)哑节点的创建(头节点)
cpp
ListNode *CreateDummyNode(void) {
ListNode *dummy = NULL;
dummy = malloc(sizeof(ListNode));
if(dummy == NULL) { // 内存申请失败,直接返回NULL(避免野指针)
perror("malloc dummy failed"); // 打印错误信息,便于调试
return NULL;
}
dummy->val = 0; // 哑节点值无意义,固定设0即可
dummy->next = NULL; // 空链表,哑节点后继为NULL
return dummy;
}
(2)头插法
cpp
int InsertHeadNode(ListNode *dummy, DataType val) {
ListNode *newNode = malloc(sizeof(ListNode));
if(newNode == NULL) {
perror("malloc newNode failed");
return -1;
}
newNode->val = val;
// 核心逻辑:先连后继,再连前驱(避免链表断裂)
newNode->next = dummy->next;
dummy->next = newNode;
return 0;
}
(3)尾插法
cpp
int InsertTailNode(ListNode *dummy, DataType val) {
ListNode *newNode = malloc(sizeof(ListNode));
if(newNode == NULL) {
perror("malloc newNode failed");
return -1;
}
newNode->val = val;
newNode->next = NULL;
ListNode *p = dummy;
while(p->next != NULL) {
p = p->next;
}
p->next = newNode;
return 0;
}
(4)指定位置插入(在第pos个有效节点后插入,pos从1开始)
cpp
int InsertPosNode(ListNode *dummy, int pos, DataType val) {
ListNode *p = dummy->next;
int curPos = 1;
while(p != NULL && curPos < Pos) {
p = p->next;
curPos++;
}
if(p == NULL) {
prinf("error: pos out of range\n");
return -1;
}
ListNode *newNode = malloc(sizeof(ListNode));
if(newNode == NULL) {
perror("malloc newNode failed");
return -1;
}
newNode->val = val;
newNode->next = p->next;
p->next = newNode;
return 0;
}
(5)指定值删除(删除第一个值为val的有效节点)
cpp
int DeleteNode(ListNode *dummy, DataType val) {
ListNode *pre = dummy;
ListNode *cur = dummy;
while(cur != NULL && cur->val != val) {
pre = cur;
cur = cur->next;
}
if(cur == NULL) {
printf("error: val %d not found\n", val);
return -1;
}
pre->next = cur->next;
free(cur);
cur = NULL;
return 0
}
(6)遍历链表
cpp
void TraverseLinkList(ListNode *dummy) {
ListNode *p = dummy->next;
if(p == NULL) {
printf("链表为空\n");
return;
}
printf("链表有效节点:\n");
while(p != NULL) {
printf("%d ", val);
p = p->next;
}
printf("\n");
}
(7)销毁链表
cpp
void DestroyLinkList(ListNode **dummy) {
if(*dummy == NULL) return;
ListNode *p = *dummy;
ListNode *tmp = NULL:
while(p != NULL) {
tmp = p->next;
free(p);
p = tmp;
}
*dummy = NULL;
}
(8)查找第一个值为val的节点
cpp
ListNode *FindNode(ListNode *dummy, DataType val) {
ListNode *cur = dummy->next;
while(cur != NULL) {
if(cur->val == val) {
return cur;
}
cur = cur->next;
}
printf("info: val %d not found in list\n", val);
return NULL;
}
(9)替换第一个值为oldVal的节点为newVal
cpp
int ReplaceNode(ListNode *dummy, DataType oldVal, DataType newVal) {
ListNode *target = FindNode(dummy, oldVal);
if(target != NULL) {
target->val = newVal;
printf("%d替换为%d成功\n", oldVal, newVal);
return 0;
}
printf("替换失败,%d未找到\n");
return -1;
}
以上就是对单链表的一个基本的操作,大家多去熟悉熟悉,多敲代码,就会记住的,好了,那我们就下次在接着讲了。再见!