数据结构:
1.逻辑结构:
线性结构:一对一
树形结构:一对多
图形结构:多对多
2.存储结构:
顺序存储:访问元素方便,插入和删除效率低,无法利用小空间
链式存储:插入删除效率高,可以利用小空间。访问元素不方便,增加额外的空间开销
索引存储
散列存储
顺序表
基本概念:
顺序表是线性表的一种顺序存储结构,简单的说就是线性结构的'数组实现',本质上等同于数组,通过申请堆区空间存储数据,通过首地址对空间进行访问
- 连续存储 :所有元素在内存中占用一块连续的地址空间(类似数组),因此可以通过下标直接访问任意元素(随机访问特性)。
- 逻辑顺序=物理顺序:第 i 个元素的物理位置就是数组的第 i 个位置(假设从0开始索引),不需要额外指针维护逻辑关系。
示例代码:
下面是一个示例代码,封装的数据结构,是顺序表,可以通过给定的len,进行动态改变数组,即动态数组,多文件编程,以及Makefile
seqlist.c
包含两个函数:在堆区中开辟连续的空间,以及使用完对空间进行销毁,不然会出现溢出
cpp
#include <stdio.h>
#include <stdlib.h>
#include "seqlist.h"
DataType *CreateSeqlist(int len)
{
DataType *pret = NULL;
pret = malloc(len * sizeof(DataType));
if (NULL == pret)
{
perror("fail to malloc");
return NULL;
}
return pret;
}
void DestorySeqlist(DataType **pparray)
{
free(*pparray);
*pparray = NULL;
return;
}
seqlist.h
cpp
#ifndef __SEQLIST_H__
#define __SEQLIST_H__
typedef int DataType;
extern DataType *CreateSeqlist(int len);
extern void DestorySeqlist(DataType **pparray);
#endif
main.c
cpp
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "seqlist.h"
int main(void)
{
DataType *parray = NULL;
int i = 0;
parray = CreateSeqlist(5);
parray[0] = 1;
parray[1] = 2;
parray[2] = 3;
parray[3] = 4;
parray[4] = 5;
for (i = 0; i < 5; i++)
{
printf("parray[%d] = %d\n", i, parray[i]);
}
DestorySeqlist(&parray);
printf("parray = %p\n", parray);
return 0;
}
Makefile
cpp
OBJ:=seqlist
OBJS+=seqlist.c
OBJS+=main.c
$(OBJ):$(OBJS)
gcc $^ -o $@
.PHONY:
clean:
rm $(OBJ)
链表
链表分类:单向链表,双向链表,循环链表(在linux内核,freertos中使用循环链表)
单向链表基础
单向链表分成有头,无头,一般不用无头,过于麻烦。
代码中的定义
Date:数据域
pNext :地址域
第一个节点为空白节点为有头链表,若为正常节点则为无头节点
链表的插入(头插法和尾插法)
1.申请节点空间
2.存放数据到节点的Date
3.将pNext赋值空白节点的pNext。(若的插入第一个数据,则是NULL)
4.将空白节点的pNext赋值为新申请的节点
插入函数:InsertHeadlinklist

cpp
int InsertHeadNode(node_t *phead,Datetype Tmpdate)
{
node_t *pNewnode = NULL;
pNewnode = malloc(sizeof(Datetype));
if(NULL == pNewnode)
{
perror("fail to malloc");
return -1;
}
pNewnode-> Date = Tmpdate;
pNewnode-> pNext = phead-> pNext;
phead-> pNext = pNewnode;
}
尾插法
先指到最后一个数据
cpp
int InsertTailNode(node_t *pTail, Datetype Tmpdate)
{
node_t *pNewnode = NULL;
// 1. 为新节点申请内存
pNewnode = malloc(sizeof(node_t)); // 注意:这里应该是sizeof(node_t),而不是sizeof(Datetype)
if (NULL == pNewnode)
{
perror("fail to malloc");
return -1;
}
// 2. 初始化新节点
pNewnode->Date = Tmpdate;
pNewnode->pNext = NULL; // 尾插的新节点将成为新的尾节点,其next必须为NULL
// 3. 找到链表的尾节点
// 从头节点(phead)开始,遍历直到当前节点的next为NULL
while (pTail->pNext != NULL) // 关键:判断条件是pTail->pNext,而不是pTail本身
{
pTail = pTail->pNext;
}
// 循环结束后,pTail指向了当前的尾节点
// 4. 将新节点连接到尾节点之后
pTail->pNext = pNewnode;
return 0;
}
链表遍历
这个是链表遍历使用,可以遍历到所有变量
cpp
while(p != NULL)
{
p = p->pNext;
}
这个是查询到链表最后一个值,就不再遍历
cpp
while(p != NULL)
{
p = p->pNext;
}
链表的删除
找到要删除的链表,并且删除
1.定义两个指针,一个指向空白节点,一个指向第一个有效节点
2.遍历所有节点,判断ptmp指向的,是否是为要删除的节点
3.找到要删除的节点后,将pPre->pNext赋值为pTmp->pNext
4.释放要删除的节点(空间)free
5.将pTmp指针指向写个判断的节点
cpp
int DeleteLinkNode(Node_t *pHead, DataType TmpData)
{
int cnt = 0;
Node_t *pPreNode = NULL;
Node_t *pTmpNode = NULL;
pPreNode = pHead;
pTmpNode = pHead->pNext;
while (pTmpNode != NULL)
{
if (pTmpNode->Data == TmpData)
{
pPreNode->pNext = pTmpNode->pNext;
free(pTmpNode);
pTmpNode = pPreNode->pNext;
cnt++;
}
else
{
pTmpNode = pTmpNode->pNext;
pPreNode = pPreNode->pNext;
}
}
return cnt;
}
查找函数
函数的核心功能是在单向链表中查找第一个数据域与目标值匹配的节点,并返回该节点的地址。
cpp
node_t *FindLinkNode(node_t *pHead, Datetype TmpDate)
{
node_t *pTmpnode = pHead; // 直接从pHead开始遍历
while(pTmpnode != NULL)
{
if(pTmpnode->Date == TmpDate)
{
return pTmpnode; // 找到,返回节点地址
}
pTmpnode = pTmpnode->pNext; // 无论是否找到,指针都应后移
}
return NULL; // 未找到,必须明确返回NULL
}
替换函数
将链表中所有数据域等于旧值的节点,其数据修改为新值
cpp
int ReplaceLinkNode(node_t *pHead, Datetype OldDate, Datetype NewDate)
{
node_t *pTmpnode = pHead;
while(pTmpnode != NULL)
{
if(pTmpnode->Date == OldDate)
{
pTmpnode->Date = NewDate;
}
// 无论是否修改,指针都必须后移,以遍历下一个节点
pTmpnode = pTmpnode->pNext;
}
return 0;
}
若要仅修改一个节点,可改为
cpp
// 修改第一个匹配的节点
int ReplaceFirstLinkNode(node_t *pHead, Datetype OldDate, Datetype NewDate)
{
node_t *pTarget = FindLinkNode(pHead, OldDate);
if(pTarget != NULL)
{
pTarget->Date = NewDate;
return 0; // 成功
}
return -1; // 未找到
}