夯实基础:数据结构核心概念与线性表(顺序表&链表)C语言全解析 数据结构篇

数据结构:

1.逻辑结构:

线性结构:一对一

树形结构:一对多

图形结构:多对多

2.存储结构:

顺序存储:访问元素方便,插入和删除效率低,无法利用小空间

链式存储:插入删除效率高,可以利用小空间。访问元素不方便,增加额外的空间开销

索引存储

散列存储

顺序表

基本概念:

顺序表是线性表的一种顺序存储结构,简单的说就是线性结构的'数组实现',本质上等同于数组,通过申请堆区空间存储数据,通过首地址对空间进行访问

  1. 连续存储 :所有元素在内存中占用一块连续的地址空间(类似数组),因此可以通过下标直接访问任意元素(随机访问特性)。
  1. 逻辑顺序=物理顺序:第 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; // 未找到
}
相关推荐
蜡笔小马2 小时前
8.Packing 算法
数据结构·b树
划破黑暗的第一缕曙光2 小时前
[数据结构]:4.二叉树_堆
c语言·数据结构·二叉树·
浅念-2 小时前
C语言——双向链表
c语言·数据结构·c++·笔记·学习·算法·链表
轩情吖2 小时前
数据结构-图
数据结构·c++·邻接表·邻接矩阵·最小生成树·kruskal算法·prim算法
Prince-Peng3 小时前
技术架构系列 - 详解Redis
数据结构·数据库·redis·分布式·缓存·中间件·架构
码农水水3 小时前
得物Java面试被问:消息队列的死信队列和重试机制
java·开发语言·jvm·数据结构·机器学习·面试·职场和发展
-Try hard-4 小时前
数据结构:链表常见的操作方法!!
数据结构·算法·链表·vim
wengqidaifeng4 小时前
数据结构---顺序表的奥秘(下)
c语言·数据结构·数据库
嵌入小生0074 小时前
单向链表的常用操作方法---嵌入式入门---Linux
linux·开发语言·数据结构·算法·链表·嵌入式