在我们这个系列的Post中,我会尽量用一些简明且没那么枯燥的语言、任务和简单代码来说明。完整的系列我想从面向对象到数据结构、再到机器学习和神经网络等等。目的是高中生的快速入门(因为你们已经在高中阶段学过基本的面向过程的Python语法了)和在校生的同步学习,希望我的代码块可以帮助到大家。
就和物理学当中的质能方程(E=mc²)一样,在程序的世界中,也有类似的方程:
Program = Structure + Algorithm(算法)
大家在学习《数据结构》这门课程时,我们会学习基础的数据结构和一些简单的算法,所以有的学校这门课也叫作“数据结构与算法基础”,在此基础上,我们日后就可以深入学习算法分析了。
首先我们看一些抽象的概念,对于他们我们品读一下就好,保证以后使用概念时不要太云里雾里即可。
(1)数据是对客观事物的符号表示
(2)数据元素是数据的基本单位
(3)数据项是构成数据元素的最小单位
(4)数据对象是具有相同性质的数据元素的集合
对于(2)(3)(4)来说,我们可以这样理解:
苏州产业园区有片小区商品房,一个商品房的信息记录为一个数据元素,面积、楼层、价格等等就是一个个数据项。房子信息的集合就是一个数据对象。
(5)数据结构就是相互之间存在一种或者多种特定关系的数据元素的集合,它包括三方面内容:逻辑结构、存储结构、数据运算。
Date_Structure = (D, S) [D是数据元素的有限集合,S是D上关系的有限集合]
对于(5)来说,我们在此主要需要研究的是分类中的前两个,“逻辑结构”&“存储结构”
逻辑结构分为线性和非线性的结构
存储结构分为顺序存储、链式存储、索引存储、散列存储
逻辑结构还是存储结构的这几种分类,它们有各有优劣势,具体的使用方式和Coding 我们将会在今后具体的实践中了解。
我们即刻开始实践,开启你的数据结构之旅吧!
这一长串和你学线性代数时学的n维向量差不多的东西,就是一个名字为L的一个线性表。
再此时我们需要搞清楚两点,
(1)它是具有相同数据类型的n个数据元素的有限的序列。
(2)是的直接前驱,是的直接后继。后面的数据元素依此类推,但有的时候我会直接叫它们前面那个和后面那个。
如下是一些你可以对线性表进行的基本操作,它们后方的英文仅仅是一些函数名称,教材大多会在函数后方加一些参数,让其变成一段伪代码,但在此我们按下不表。
初始化线性表(InitList)
销毁线性表(DestroyList)
清空线性表(ClearList)
判断线性表是否为空(Empty)
获取线性表长度(Length)
获取元素(GetElement)
查找元素(LocateElement)
插入元素(Insert)
删除元素(Delete)
对于顺序表的结构,也就是要定义一个线性表的顺序存储类型,在C语言中通过结构体来定义。
#include <stdio.h>
#define MAXSIZE 100 // 定义线性表的最大长度
typedef int ElemType; // 这里以int为例定义ElemType
typedef struct {
ElemType data[MAXSIZE]; // 存储线性表元素的数组
int length; // 线性表当前长度
} SeqList;
int main() {
SeqList list; // 创建一个线性表
list.length = 0; // 初始化线性表长度为0
// 可以在这儿继续添加对线性表的操作,比如插入、删除等
return 0;
}
细心观察我们就会发现,上面的线性表是由静态的数组所定义,万一添加的数据比较多,超了MAXSIZE,那么你的程序就寄了。
还好我们有指针这个家伙,在C语言的动态分配中,存储空间是在程序执行过程中通过动态内存分配,空间要是占满了,会开辟更大的空间。通常通过malloc
、realloc
和free
函数来进行。以下是一个简单的动态数组的实现,用于模拟动态列表。
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct {
ElemType* data; // 指向动态分配的数组
int capacity;
} DynamicArray;
// 初始化函数
void initDynamicArray(DynamicArray* array, int capacity) {
array->data = (ElemType*)malloc(capacity * sizeof(ElemType));
if (array->data == NULL) {
// 内存分配失败的错误处理
fprintf(stderr, "Memory allocation failed.");
exit(EXIT_FAILURE);
}
array->capacity = capacity; // 设置容量
}
int main() {
DynamicArray myArray;
initDynamicArray(&myArray, 10);
free(myArray.data);
return 0;
}
我们的基础要求是理解代码最前一部分的结构体。
在初始化函数中,我们关注下面这行代码,它使用了 malloc
函数动态分配了一个大小为 capacity * sizeof(ElemType)
字节的内存空间,然后将其地址赋给 data
指针成员。上面的代码可能对于初学者有点吓人,但我们不用着急,慢慢理解敲一敲。
array->data = (ElemType*)malloc(capacity * sizeof(ElemType));
在Python中则通过类来实现,在Python中,我们可以更灵活地定义线性表,不需要指定C语言中的"ElemType",Python是动态类型的语言,可以接受任何类型的元素。我们需要理解在Python中“动态分配”通常是自动处理的,特别是当使用列表时。不过,我们倒是可以通过手动管理列表的容量来模拟类似于C语言中动态内存分配的行为,但我们暂时就先不自找麻烦了。
class SeqList:
def __init__(self, capacity=100):
self.data = [] # 初始化存储线性表元素列表
self.capacity = capacity # 定义最大容量
接下来我们利用我们定义出的顺序表搞一点操作
显然这是一个插入操作,让我们尝试在list
的指定位置pos
插入一个新元素elem
。如果插入成功,函数返回true
,否则返回false
。
bool insert(int pos, ElemType elem) {
if (list.length >= MAXSIZE || pos < 1 || pos > list.length + 1) {
return false; // 检查是否有空间和位置是否合法
}
for (int i = list.length; i >= pos; --i) {
list.data[i] = list.data[i - 1]; // 后移元素
}
list.data[pos - 1] = elem; // 插入新元素
++list.length; // 长度加1
return true;
}
在刚刚的插入操作基础之上,我们来探索一下删除操作。类似的,我们尝试删除list
中位置为pos
的元素。如果删除成功,函数返回true
,否则返回false
。
bool delete(int pos) {
if (pos < 1 || pos > list.length) {
return false; // 位置不合法
}
for (int i = pos; i < list.length; ++i) {
list.data[i - 1] = list.data[i]; // 前移元素
}
--list.length; // 长度减1
return true;
}
接下来的按位和按值查找就相对简单了,按位查找函数就是一个查位置报数字的活计,按值查找函数需要我们利用循环一个个找,找到了就报位置。
int searchByValue(ElemType elem) {
for (int i = 0; i < list.length; ++i) {
if (list.data[i] == elem) {
return i + 1; // 从1开始计数
}
}
return -1; // 函数没有找到
}
int searchByPosition(int pos) {
if (pos < 1 || pos > list.length) {
return -1; // 位置有问题辣
}
return list.data[pos - 1];
}
C语言确实是有些许的繁杂哈,还记得我们前面的Python定义吗?有了这些操作,我们就搞一个完整的Python对象模块吧!SeqList
类成为一个完整的对象了,包括初始的构造方法和之前提到的四种操作(插入、删除、按值查找、按位查找)作为类的方法!
class SeqList:
def __init__(self, capacity=100):
self.data = [] # 初始化存储线性表元素列表
self.capacity = capacity # 定义最大容量
def insert(self, pos, elem):
if pos < 1 or pos > len(self.data) + 1 or len(self.data) >= self.capacity:
return False # 插入位置无效或列表已满
self.data.insert(pos - 1, elem)
return True
def delete(self, pos):
if pos < 1 or pos > len(self.data):
return False # 删除位置无效
del self.data[pos - 1]
return True
def searchByValue(self, elem):
try:
index = self.data.index(elem)
return index + 1 # 位置从1开始计数
except ValueError:
return -1 # 未找到元素
def searchByPosition(self, pos):
if pos < 1 or pos > len(self.data):
return None # 位置无效
return self.data[pos - 1]
在最后,让我们看一道C语言期末题目吧!学习了数据结构是不是刚入学的C编程题目都简简单单、轻轻松松了呢?
本题要求完全两个有序字符串的合并,合并后仍然保持有序。例如字符串a为acefim,字符串b为bcdghrst,则合并后的字符串为abccdefghimrst。
#include <stdio.h>
int main()
{
char a[100],b[100],c[200];
int i=0,j=0,k=0;
scanf("%s%s",a,b);
while(a[i] && b[j])
{
if(a[i]<b[j])
{
c[k] = a[i];
k++;
i++;
}
else
{
c[k]=b[j];
k++;
j++;
}
}
while(a[i])
{
c[k]=a[i];
k++;
i++;
}
while(b[j])
{
c[k]=b[j];
k++;
j++;
}
c[k] = '\0';
printf("%s",c);
return 0;
}