一、数据结构概述:
概念:计算机存储,管理,组织数据的方式方法;
常用的数据结构类型:
1.根据数据的逻辑结构(数据关系)划为:
集合: 数据元素之间除了"同属于某一个组织"外,再无其他关系。
线性数据结构: 数据之间"一对一"关系,简单理解,每一个元素具有唯一的前驱和后继;
例子:一维数组
非线性数据结构: 每一个元素不具有唯一的前驱和后继;
例子:二维数组
2.根据数据元素的存储方式划为:
顺序结构:各个元素存储在连续的内存空间中
链式结构:各个元素存储在不连续的内存空间中
索引结构:元素在存储时,不仅仅存储元素数据,还要为数据建立索引表标识元素的地址;
哈希结构:元素在存储时,为元素设置关键字(key),在访问元素时,根据关键字访问元素。
常见的数据结构:
链表,树型结构,栈,图,映射,散列表...
二、数据结构的设计实现:
引入问题:根据之前所学的知识内容,我们对同类型的多个数据可以利用数组来存储管理,对不同类型的多个数据
可以利用结构体来存储的,但不论是数组还是结构体,数据存储都是在连续的内存空间中,那么如果此时内存中不存在连续的内存空间用于存储多个数据,此时的数据该如何存储?
解决方案:链表
1.链表的应用场景
链表是一种动态的数据结构,比较适合于事先无法获知待存存储的数据的数据数量,可能需要随时增加数据;
比较始合于对数据的操作是频繁的增加和删除的情况。
2.单向链表的设计实现
1)单向链表的组成:
头指针 (指向头结点的指针变量)
节点/结点
数据域
指针域
typedef int data_t;
typedef struct linklist
{
data_t data;
struct linklist* next;
}linklist_t;
2)链表的相关操作算法(增,删,改,查) 注:链表的操作是在堆上进行的
2.1链表创建:
第一步:首先建立头文件linklist.h
#ifndef __LINKLIST_H //预处理用于条件编译 避免头文件反复包含
#define __LINKLIST_H
typedef int data_t;
typedef struct linklist
{
data_t data;
struct linklist* next;
}linklist_t;
int list_create(linklist_t** head, data_t data);
#endif
第二步:创建建立链表函数 linklist.c
#include "linklist.h"
#include<stdio.h>
#include<stdlib.h>
//创建节点 输出型参数返回地址(指针)
int list_create(linklist_t** head,data_t data) //一级指针的时候此时head还是值传递 所以需要用二级指针对head指针的地址进行传递
{
linklist_t *p = (linklist_t*)malloc(sizeof(linklist_t)); //申请一个节点空间
if (p == NULL)
return -1;
p->data = data;
p -> next = NULL;
*head = p; //通过对头指针解引用
return p;
}
第三步:创建main.c函数进行相关操作
#include "linklist.h" //不能多次重复包含 会报错
#include<stdio.h>
int main()
{
linklist_t* head = NULL;
list_create(&head,321); //接收指针的地址
printf("%d\n", head->data);
return 0;
}
2.2增加数据(数据可插入头部,中间,尾部)
①头插法:
//在头结点插入元素(头插法)
int list_addhead(linklist_t** head, data_t data)
{
linklist_t* p = (linklist_t*)malloc(sizeof(linklist_t));
if (p == NULL)
return -1;
p->data = data;
p->next = *head; //将要插入数据节点的后驱指向头结点的地址
*head = p; //通过对头指针解引用
return 0;
}
②尾插法:
//在链表尾部插入数据(尾插法)
int list_addtail(linklist_t** head, data_t data)
{
linklist_t* pnew = (linklist_t*)malloc(sizeof(linklist_t));
if (pnew == NULL)
return -1;
pnew->data = data;
pnew->next = NULL;
linklist_t* p = *head, * q = NULL; //利用快慢指针 要对head解引用
if (p == NULL)
{
*head = pnew; //p为空,则head链表是空链表,所以将要插入的节点就为头节点
return 0;
}
//遍历链表
while (p->next)
{
p = p->next;
}
p->next = pnew;
return 0;
}
③中间插法:
//中间插法
int list_insert(linklist_t** head, data_t newdata, data_t olddata)
{
linklist_t* pnew = (linklist_t*)malloc(sizeof(linklist_t));
if (pnew == NULL)
return -1;
pnew->data = newdata;
pnew->next = NULL;
linklist_t* p = *head, * q = NULL;
if (p == NULL) //链表为空,用新数据创建链表
{
*head = pnew;
return 0;
}
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0) //插入的位置为头节点位置
{
pnew->next = p;
*head = pnew;
return 0;
}
while (p)
{
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0) //插入到链表中的某节点位置
{
if (p->next == NULL)
{
p->next = pnew;
pnew->next = NULL;
return 0;
}
else
{
q = p->next;
p->next = pnew;
pnew->next = q;
return 0;
}
}
q = p; //使用快慢指针
p = p->next;
}
//未找到插入位置,将新节点尾插到链表中
q->next = pnew;
pnew->next = NULL;
return 0;
}
2.3 删除数据
//删除节点
int list_delete(linklist_t** head, data_t data)
{
linklist_t* p = *head,*q = NULL;
if (p == NULL)
return -1;
if (memcmp(&p->data, &data, sizeof(data_t)) == 0) //删除的是头节点
{
if (p->next == NULL) //头节点是唯一的节点
{
free(p);
return 0;
}
else //头节点不是唯一的节点
{
*head = p->next;
free(p);
return 0;
}
}
while (p) //进入循环后表示删除的不是头节点
{
if (memcmp(&p->data, &data, sizeof(data_t)) == 0) //数据找到
{
q->next = p->next;
free(p);
return 0;
}
q = p;
p = p->next;
}
return -1;
}
2.4修改数据
//修改数据
int list_updata(linklist_t** head, data_t olddata, data_t newdata)
{
linklist_t* p = *head;
if (p == NULL)
return -1;
while (p)
{
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0)
{
p->data = newdata;
return 0;
}
p = p->next;
}
return -1;
}
2.5查找数据
//查找数据
linklist_t* list_find(linklist_t* head, data_t data)
{
linklist_t* p = head;
if (p == NULL)
return -1;
while (p)
{
if (memcmp(&p->data, &data, sizeof(data_t)) == 0)
{
printf("找到了%d\n", data);
return 0;
}
p = p->next;
}
return -1;
}
2.6遍历链表
//遍历链表
int list_showall(linklist_t* head) //不涉及对头指针地址的修改,所以用一级指针即可
{
linklist_t* p = head;
if (p == NULL)
return -1;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
return 0;
}
2.7回收链表
//回收链表
int list_free(linklist_t** head)
{
linklist_t* p = *head, * q = NULL;
while (p)
{
q = p;
p = p->next;
free(q);
}
*head = NULL;
return 0;
}
附完整代码:
linklist.h
#ifndef __LINKLIST_H //预处理用于条件编译 避免头文件反复包含
#define __LINKLIST_H
//双向链表
typedef int data_t;
typedef struct linklist
{
data_t data;
struct linklist* next;
}linklist_t;
int list_create(linklist_t** head, data_t data);
int list_addhead(linklist_t** head, data_t data);
int list_insert(linklist_t** head, data_t newdata, data_t olddata);
int list_addtail(linklist_t** head, data_t data);
int list_updata(linklist_t** head, data_t olddata, data_t newdata);
linklist_t* list_find(linklist_t* head, data_t data);
int list_delete(linklist_t** head, data_t data);
int list_showall(linklist_t* head);
int list_free(linklist_t** head);
#endif
linklist.c
#include "linklist.h"
#include<stdio.h>
#include<stdlib.h>
//创建节点 输出型参数返回地址(指针)
int list_create(linklist_t** head, data_t data) //一级指针的时候此时head还是值传递 所以需要用二级指针对head指针的地址进行传递
{
linklist_t* p = (linklist_t*)malloc(sizeof(linklist_t)); //申请一个节点空间
if (p == NULL)
return -1;
p->data = data;
p->next = NULL;
*head = p; //通过对头指针解引用
return p;
}
//在头结点插入元素(头插法)
int list_addhead(linklist_t** head, data_t data)
{
linklist_t* p = (linklist_t*)malloc(sizeof(linklist_t));
if (p == NULL)
return -1;
p->data = data;
p->next = *head; //将要插入数据节点的后驱指向头结点的地址
*head = p;
return 0;
}
//中间插法
int list_insert(linklist_t** head, data_t newdata, data_t olddata)
{
linklist_t* pnew = (linklist_t*)malloc(sizeof(linklist_t));
if (pnew == NULL)
return -1;
pnew->data = newdata;
pnew->next = NULL;
linklist_t* p = *head, * q = NULL;
if (p == NULL) //链表为空,用新数据创建链表
{
*head = pnew;
return 0;
}
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0) //插入的位置为头节点位置
{
pnew->next = p;
*head = pnew;
return 0;
}
while (p)
{
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0) //插入到链表中的某节点位置
{
if (p->next == NULL)
{
p->next = pnew;
pnew->next = NULL;
return 0;
}
else
{
q = p->next;
p->next = pnew;
pnew->next = q;
return 0;
}
}
q = p; //使用快慢指针
p = p->next;
}
//未找到插入位置,将新节点尾插到链表中
q->next = pnew;
pnew->next = NULL;
return 0;
}
//在链表尾部插入数据(尾插法)
int list_addtail(linklist_t** head, data_t data)
{
linklist_t* pnew = (linklist_t*)malloc(sizeof(linklist_t));
if (pnew == NULL)
return -1;
pnew->data = data;
pnew->next = NULL;
linklist_t* p = *head, * q = NULL; //利用快慢指针 要对head解引用
if (p == NULL)
{
*head = pnew; //p为空,则head链表是空链表,所以将要插入的节点就为头节点
return 0;
}
//遍历链表
while (p->next)
{
p = p->next;
}
p->next = pnew;
return 0;
}
//修改数据
int list_updata(linklist_t** head, data_t olddata, data_t newdata)
{
linklist_t* p = *head;
if (p == NULL)
return -1;
while (p)
{
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0)
{
p->data = newdata;
return 0;
}
p = p->next;
}
return -1;
}
//查找数据
linklist_t* list_find(linklist_t* head, data_t data)
{
linklist_t* p = head;
if (p == NULL)
return -1;
while (p)
{
if (memcmp(&p->data, &data, sizeof(data_t)) == 0)
{
printf("找到了%d\n", data);
return 0;
}
p = p->next;
}
return -1;
}
//删除节点
int list_delete(linklist_t** head, data_t data)
{
linklist_t* p = *head,*q = NULL;
if (p == NULL)
return -1;
if (memcmp(&p->data, &data, sizeof(data_t)) == 0) //删除的是头节点
{
if (p->next == NULL) //头节点是唯一的节点
{
free(p);
return 0;
}
else //头节点不是唯一的节点
{
*head = p->next;
free(p);
return 0;
}
}
while (p) //进入循环后表示删除的不是头节点
{
if (memcmp(&p->data, &data, sizeof(data_t)) == 0) //数据找到
{
q->next = p->next;
free(p);
return 0;
}
q = p;
p = p->next;
}
return -1;
}
//回收链表
int list_free(linklist_t** head)
{
linklist_t* p = *head, * q = NULL;
while (p)
{
q = p;
p = p->next;
free(q);
}
*head = NULL;
return 0;
}
//遍历链表
int list_showall(linklist_t* head) //不涉及对头指针地址的修改,所以用一级指针即可
{
linklist_t* p = head;
if (p == NULL)
return -1;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
return 0;
}
main.c
#pragma warning(disable:4996)
#include "linklist.h" //不能多次重复包含 会报错
#include<stdio.h>
int main()
{
linklist_t* head = NULL;
list_create(&head, 321); //接收指针的地址
list_addhead(&head, 666);//头插法
//中间插法
list_addtail(&head, 222); //尾插法
list_addtail(&head, 888);
list_addtail(&head, 999);
list_showall(head); //遍历链表
list_find(head, 666);
/*while (1)
{
data_t data;
printf("请输入要删除的数据(-1退出):\n");
scanf("%d", &data);
if (data == -1)
break;
if (-1 == list_delete(&head, data))
{
printf("删除的数据不存在,请重试...\n");
continue;
}
list_showall(head); //遍历链表
}
*/
while (1)
{
#if 0 //预处理条件编译
data_t data;
printf("请输入要删除的数据(-1退出):\n");
scanf("%d", &data);
if (data == -1)
break;
if (-1 == list_delete(&head, data))
{
printf("删除的数据不存在,请重试...\n");
continue;
}
list_showall(head); //遍历链表
#else
data_t olddata, newdata;
printf("请输入要在那个数据前插入(-1退出):\n");
scanf("%d", &olddata);
if (olddata == -1)
break;
printf("请输入插入的数据(-1退出):\n");
scanf("%d", &newdata);
if (-1 == list_insert(&head, newdata, olddata))
{
printf("数据插入失败,请重试...\n");
continue;
}
list_showall(head); //遍历链表
#endif
}
list_free(&head); //回收链表
return 0;
}