数据结构学习笔记

本文介绍了数据结构中的线性表,包括顺序表和链式存储的单链表、双链表以及循环链表的概念、特点和操作。详细讲解了各种链表的插入、删除、查找等基本操作,并探讨了存储结构对算法性能的影响,如时间复杂度和空间复杂度。
摘要由CSDN通过智能技术生成

数据结构学习笔记

一、绪论

1.1 数据结构的基本概念

01、基本概念
  • 数据
  • 数据元素、数据项
  • 数据对象、数据结构
  • 数据类型、抽象数据类型(ADT)
02、三要素
  • 逻辑结构
    • 线性结构
    • 非线性结构
      • 集合
      • 树形结构
      • 图结构(网状结构)
  • 物理结构(存储结构)
    • 顺序存储
    • 非顺序存储
      • 链式存储
      • 索引存储
      • 散列存储
  • 数据的运算
    • 根据逻辑结构来定义,根据存储结构来实现

1.2 算法的基本概念

01、什么是算法
  • 程序=数据结构+算法
    • 数据结构要处理的信息
    • 算法是处理信息的步骤
02、算法的五个特性
  • 有穷性
    • 有穷时间内能执行完
      • 算法是有穷的
      • 程序可以是无穷的
  • 确定性
    • 输入相同输入只会产生相同输出
  • 可行性
    • 可以用已有的基本操作实现算法
  • 输入
    • 丢给算法处理的数据
  • 输出
    • 算法处理的结果
03、”好“算法的特质
  • 正确性
    • 能正确解决问题
  • 可读性
    • 对算法的描述要让其他人也看得懂
  • 健壮性
    • 算法能处理一些异常状况
  • 高效率与低存储量需求
    • 即算法执行省时、省内存
    • 时间复杂度低、空间复杂度低

1.3 时间复杂度

01、如何计算
  1. 找到一个基本操作(最深层循环)
  2. 分析该基本操作的执行次数x与问题规模n的关系x=f(n)
  3. x的数量级O(x)就是算法时间复杂度T(n)
02、常用技巧
  • 加法规则:O(f(n)) + O(g(n)) = O(max(f(n), g(n)))
  • 乘法规则:O(f(n)) + O(g(n)) = O(f(n) x g(n))
  • “常对幂指阶”:O(1) < O(log2n) < O(n) < O(nlog2n) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
03、三种复杂度
  • 最坏时间复杂度:考虑输入数据”最坏“的情况
  • 平均时间复杂度:考虑所有输入数据都等概率出现的情况
  • 最好时间复杂度:考虑输入数据”最好“的情况

1.4 空间复杂度

01、如何计算
  • 普通程序

    1. 找到所占空间大小与问题规模相关的变量
    2. 分析所占空间x与问题规模n的关系 x=f(n)
    3. x的数量级O(x)就是算法空间复杂度S(n)
  • 递归程序

    1. 找到递归调用的深度x与问题规模的关系 x=f(n)

    2. x的数量级O(x)就是算法空间复杂度S(n)

      注:有的算法各层函数所需存储空间不同,分析方法略有区别

02、常用技巧
  • 加法规则:O(f(n)) + O(g(n)) = O(max(f(n), g(n)))
  • 乘法规则:O(f(n)) + O(g(n)) = O(f(n) x g(n))
  • “常对幂指阶”:O(1) < O(log2n) < O(n) < O(nlog2n) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)

二、线性表

2.1 线性表的定义和基本操作

01、定义(逻辑结构)
  • 值得注意的特性:数据元素同类型、有限、有序
  • 重要术语
    • 表长、空表
    • 表头、表尾
    • 前驱、后继
    • 数据元素的位序(从1开始)
02、基本操作(运算)
  • 创销、增删改查(所有数据结构适用的记忆思路)
  • 判空、判长、打印输出(还可以会根据实际需求增加其他基本操作)
  • 其他值得主要的点:
    • 理解什么时候要传入参数的引用“&”
    • 函数命名要有可读性

2.2 顺序表的定义(线性表的顺序表示)

01、存储结构
  • 逻辑上相邻的数据元素物理上也相邻
02、实现方式
  • 静态分配

    • 使用“静态数组”实现
    • 大小一旦确定就无法改变
    //顺序表的实现方式--静态分配
    #include<stdio.h>
    #include<stdbool.h>			//C语言中没有布尔类型
    #define MaxSize 10			//定义最大长度
    typedef struct {
    	int data[MaxSize];		//用静态的“数组”存放数据元素
    	int length;				//顺序表的当前长度
    }SqList;					//顺序表的类型定义
    //初始化顺序表
    void InitList(SqList *L) {
    	for (int i = 0; i < MaxSize; i++)
    		L->data[i] = 0;		//将所有元素设置为默认初始值
    	L->length = 0;			//顺序表的初始长度0
    }
    
    //顺序表的插入
    bool ListInsert(SqList* L, int i, int e) { //在顺序表L的位序i插入元素e
    	if (i < 1 || i > L->length+1)//判断i的范围是否有效
    		return false;
    	if (L->length >= MaxSize)  //存储空间已达到最大长度,不能继续插入
    		return false;
    	for (int j = L->length; j > i - 1; j--)//将第i个元素及以后的元素后移
    		L->data[j] = L->data[j - 1];
    	L->data[i - 1] = e;  //在位序为i的位置插入元素e
    	L->length++;         //长度+1
    	return true;
    }
    //顺序表的删除
    bool ListDelete(SqList* L, int i) {//在顺序表L的位序i删除元素
    	if (i < 1 || i > L->length)//判断i的范围是否有效
    		return false;
    	for (int j = i - 1; j < L->length; j++)//将第i个元素及以后的元素前移
    		L->data[j] = L->data[j + 1];
    	L->length--;		//长度-1
    	return true;
    
    }
    
    //按位查找
    int GetElem(SqList L, int i) {//获取第i位序的元素
    	if (i < 1 || i > L.length) {//判断i的范围是否有效
    		printf("查找位序不合法!");
    		return -1;		//返回-1查找失败
    	}
    	return L.data[i - 1];//获取第i位序的元素
    }
    //按元素值查找
    int LocatieElem(SqList L, int e) {//获取顺序表L中第一个等于e的元素
    	for (int i = 0; i < L.length; i++)//遍历顺序表L
    		if (L.data[i] == e)		//判断元素值是否等于e
    			return i + 1;		//获取第i位序的元素
    	return 0;					//返回0为没有找到
    }
    
    //调用
    int main() {
    	SqList L;				//声明一个顺序表
    	InitList(&L);			//初始化顺序表
    	ListInsert(&L, 1, 1);		//在第1个位置上插入元素1
    	ListInsert(&L, 2, 2);		//在第1个位置上插入元素1
    	ListInsert(&L, 3, 3);		//在第1个位置上插入元素1
    	ListInsert(&L, 4, 2);		//在第1个位置上插入元素1
    	ListDelete(&L, 5);			//删除第5个位置上的元素
    	for (int i = 0; i < L.length; i++)
    		printf("%d\n", L.data[i]);//查看当前数据表中的数据
    	printf("数据表长度:%d\n", L.length);	//查看当前顺序表长度
    	printf("第2位的元素为:%d\n", GetElem(L, 2));//查看第2位的元素的值
    	printf("第一个元素值为2的位序为:%d", LocatieElem(L, 2));//查看第一个元素值为2的位序为
    	return 0;
    }
    
  • 动态分配

    • 使用“动态数组”实现
    • L.data=(ElemType *)malloc(sizeof(ElemType) * size);
    • 顺序表存满时,可再用malloc动态拓展顺序表的最大容量
    • 需要将数据元素复制到新的存储区域,并用free函数释放原区域
    //顺序表的实现方式--动态分配
    #include<stdio.h>
    #include<stdbool.h>												//C语言中没有布尔类型
    #include<stdlib.h>												//malloc、free函数的头文件
    #define InitSize 10												//默认的表长度的初始定义
    typedef struct {
    	int* data;													//指示动态分配数组的指针
    	int MaxSize;												//顺序表的最大容量
    	int length;													//顺序表的当前长度
    }SeqList;
    //初始化顺序表
    void InitList(SeqList* L) {
    	L->data = (int*)malloc(InitSize * sizeof(int));				//用malloc函数申请一片连续的存储空间
    	L->length = 0;												//初始化长度为0
    	L->MaxSize = InitSize;										//初始化最大长度为默认表长度
    }
    
    //增加动态数组的长度
    void IncreaseSize(SeqList* L, int len) {
    	int* p = L->data;											//定义一个p指针指向顺序表L中的存储空间
    	L->data = (int*)malloc((L->MaxSize + len) * sizeof(int));   //用malloc函数申请长度为MaxSize+len连续的存储空间
    	for (int i = 0; i < L->length; i++) {
    		L->data[i] = p[i];										//将数据复制到新区域
    	}
    	L->MaxSize = L->MaxSize + len;								//顺序表最大长度增加 len
    	free(p);													//释放原来的内存空间
    }
    
    //顺序表的销毁
    bool DestroyList(SeqList* L) {
    	if (L->data) {
    		free(L->data);					//释放L->data指向的内存空间
    		L->data = NULL;					//释放L->data本身
    		L->length = 0;					//将顺序表长度置为0
    		L->MaxSize = 0;					//最大长度也置为0
    	}
    	else
    		return false;
    	return true;
    }
    
    //顺序表的插入
    bool ListInsert(SeqList* L, int i, int e) { //在顺序表L的位序i插入元素e
    	if (i < 1 || i > L->length+1)//判断i的范围是否有效
    		return false;
    	if (L->length >= L->MaxSize)  //存储空间已达到最大长度,不能继续插入
    		return false;
    	for (int j = L->length; j > i - 1; j--)//将第i个元素及以后的元素后移
    		L->data[j] = L->data[j - 1];
    	L->data[i - 1] = e;  //在位序为i的位置插入元素e
    	L->length++;         //长度+1
    	return true;
    }
    //顺序表的删除
    bool ListDelete(SeqList* L, int i) {//在顺序表L的位序i删除元素
    	if (i < 1 || i > L->length)//判断i的范围是否有效
    		return false;
    	for (int j = i - 1; j < L->length; j++)//将第i个元素及以后的元素前移
    		L->data[j] = L->data[j + 1];
    	L->length--;		//长度-1
    	return true;
    
    }
    
    //按位查找
    int GetElem(SeqList L, int i) {//获取第i位序的元素
    	if (i < 1 || i > L.length) {//判断i的范围是否有效
    		printf("查找位序不合法!");
    		return -1;		//返回-1查找失败
    	}
    	return L.data[i - 1];//获取第i位序的元素
    }
    //按元素值查找
    int LocatieElem(SeqList L, int e) {//获取顺序表L中第一个等于e的元素
    	for (int i = 0; i < L.length; i++)//遍历顺序表L
    		if (L.data[i] == e)		//判断元素值是否等于e
    			return i + 1;		//获取第i位序的元素
    	return 0;					//返回0为没有找到
    }
    
    //调用
    int main() {
    	SeqList L;													//声明一个顺序表
    	InitList(&L);												//初始化顺序表
    	ListInsert(&L, 1, 1);										//在第1个位置上插入元素1
    	ListInsert(&L, 2, 2);										//在第1个位置上插入元素2
    	ListInsert(&L, 3, 3);										//在第1个位置上插入元素3
    	ListInsert(&L, 4, 2);										//在第1个位置上插入元素2
    	for (int i = 0; i < L.length; i++)
    		printf("%d\n", L.data[i]);								//查看当前数据表中的数据
    	ListDelete(&L, 3);											//删除第5个位置上的元素
    	printf("数据表长度:%d\n", L.length);						//查看当前顺序表长度
    	printf("第2位的元素为:%d\n", GetElem(L, 2));				//查看第2位的元素的值
    	printf("第一个元素值为2的位序为:%d\n", LocatieElem(L, 2));//查看第一个元素值为2的位序为
    	printf("增加之前长度:%d\n", L.MaxSize);
    	IncreaseSize(&L, 5);										//顺序表L中最大的数组长度+5
    	printf("增加之后长度:%d\n", L.MaxSize);
    	DestroyList(&L);											//销毁表
    	return 0;
    }
    
03、特点
  • 随机访问:能在O(1)时间内找到第i个元素
  • 存储密度高
  • 拓展容量不方便
  • 插入、删除数据元素不方便

2.3 单链表的定义

  • 单链表

    • 用“链式存储”(存储结构)实现了“线性结构”(逻辑结构)
    • 一个节点存储一个数据元素
    • 各结点间的先后关系用一个指针表示
  • 用代码定义一个单链表

    //单链表的定义
    typedef struct Node {
    	int data;			//定义结点int类型的数据域
    	struct Node* next;	//指针指向下一个结点
    }Node, * LinkList;
    
  • 两种实现:

    • 不带头结点

      //单链表的实现方式——不带头结点
      //单链表的实现方式——带头结点
      #include<stdio.h>
      #include<stdbool.h>												//C语言中没有布尔类型
      #include<stdlib.h>												//malloc、free函数的头文件
      //初始化一个单链表(不带头结点)
      bool InitList(LinkList *L) {
      	(*L) = NULL;			//空表暂时没有任何结点
          printf("初始化成功!\n");
      	return true;
      }
      
      • 空表判断:L==NULL。写代码不方便
    • 带头结点(头结点不存数据,只是为了操作方便)

      //单链表的实现方式——带头结点
      #include<stdio.h>
      #include<stdbool.h>												//C语言中没有布尔类型
      #include<stdlib.h>												//malloc、free函数的头文件
      //初始化一个单链表(带头结点)
      bool InitList(LinkList *L) {
      	(*L) = (Node*)malloc(sizeof(Node));//分配一个头结点申请内存空间
      	if ((*L) == NULL)					//内存不足分配失败
      		return false;
      	(*L)->next = NULL;					//头结点之后暂时没有结点
      	printf("初始化成功!\n");
      	return true;
      }
      
      • 空表判断:L->next == NULL。写代码更方便
  • 其他值得注意的点:

    • typedef关键字的用法
    • “LinkList”等价于“LNode *”前者强调这是链表,后者强调这是结点,合适的地方使用合适的名字,代码可读性更高
    定义
    //单链表的实现方式——带头结点
    #include<stdio.h>
    #include<stdbool.h>												//C语言中没有布尔类型
    #include<stdlib.h>												//malloc、free函数的头文件
    
    //单链表的定义
    typedef struct Node {
    	int data;			//定义结点int类型的数据域
    	struct Node* next;	//指针指向下一个结点
    }Node,*LinkList;
    
    初始化
    //初始化一个单链表(带头结点)
    bool InitList(LinkList *L) {
    	(*L) = (Node*)malloc(sizeof(Node));//分配一个头结点申请内存空间
    	if ((*L) == NULL)					//内存不足分配失败
    		return false;
    	(*L)->next = NULL;					//头结点之后暂时没有结点
    	printf("初始化成功!\n");
    	return true;
    }
    //判断单链表是否为空
    bool Empty(LinkList L) {
    	if (L->next == NULL)			//带头结点的下一个结点是否为空
    		return true;
    	else
    		return false;
    }
    
    插入操作
    //单链表按位序插入(带头结点)
    bool ListInsert(LinkList *L, int i, int e) {
    	if (i < 1)						//判断插入位序是否合法
    		return false;
    	Node* p;						//指针p指向当前扫描到的结点
    	int j = 0;						//当前p指向的是第几个结点
    	p = (*L);							//L指向头结点,头结点是第0个(不存数据)
    	while (p != NULL && j < i - 1) {//循环找到第i-1个结点
    		p = p->next;
    		j++;
    	}
    	if (p == NULL)					//指向最后一个结点为空,插入不合法
    		return false;
    	Node* s = (Node*)malloc(sizeof(Node));//给待插入的结点申请内存空间
    	s->data = e;					//将新结点数据域赋值为e
    	s->next = p->next;				//新结点的指针指向当前p结点的下一个结点
    	p->next = s;					//将结点s连接到p之后
    	return true;
    }
    //后插操作
    bool InsertNextNode(Node* p, int e) {			//在p结点之后插入元素e
    	if (p == NULL)					//指向最后一个结点为空,插入不合法
    		return false;
    	Node* s = (Node*)malloc(sizeof(Node));//给待插入的结点申请内存空间
    	if (s == NULL)					//内存分配失败
    		return false;
    	s->data = e;					//将新结点数据域赋值为e
    	s->next = p->next;				//新结点的指针指向当前p结点的下一个结点
    	p->next = s;					//将结点s连接到p之后
    	return true;
    }
    //前插操作
    bool InsertPriorNode(Node* p, int e) {			//在p结点之前插入元素e
    	if (p == NULL)					//指向最后一个结点为空,插入不合法
    		return false;
    	Node* s = (Node*)malloc(sizeof(Node));//给待插入的结点申请内存空间
    	if (s == NULL)					//内存分配失败
    		return false;
    	s->next = p->next;				//新结点的指针指向当前p结点的下一个结点
    	p->next = s;					//将结点s连接到p之后
    	s->data = p->data;
    	s->data = e;					//将新结点数据域赋值为e
    	return true;
    }
    
    删除操作
    //按位序删除(带头结点)
    bool ListDelete(LinkList *L,int i, int e) {					//删除表L中第i个位置的元素,并用e返回删除元素的值
    	if (i < 1)						//判断删除位序是否合法
    		return false;
    	Node* p;						//指针p指向当前扫描到的结点
    	int j = 0;						//当前p指向的是第几个结点
    	p = (*L);							//L指向头结点,头结点是第0个(不存数据)
    	while (p != NULL && j < i - 1) {//循环找到第i-1个结点
    		p = p->next;
    		j++;
    	}
    	if (p == NULL)					//指向最后一个结点为空,删除不合法
    		return false;
    	Node* q = p->next;				//令q指向被删除结点
    	e = q->data;					//用e返回元素的值
    	p->next = q->next;				//将*q结点从链中“断开”
    	free(q);						//释放结点的存储空间
    	printf("删除结点的值为:%d\n", e);
    	return true;
    }
    //删除指定结点(删除的为最后一个结点时出现BUG)
    bool DeleteNode(Node* p) {
    	if (p == NULL)					//指向最后一个结点为空,删除不合法
    		return false;
    	Node* q = p->next;				//令q指向*p的后继结点
    	p->data = p->next->data;		//和后继结点交换数据与
    	p->next = q->next;				//将*q结点从链中“断开”
    	free(q);						//释放结点的存储空间
    	return true;
    }
    
    查找操作
    //按位查找(带头结点)
    Node* GetElem(LinkList L, int i) {  //返回第i个元素
    	if (i < 1)						//判断插入位序是否合法
    		return NULL;
    	Node* p;						//指针p指向当前扫描到的结点
    	int j = 0;						//当前p指向的是第几个结点
    	p = L;							//L指向头结点,头结点是第0个(不存数据)
    	while (p != NULL && j < i - 1) {//循环找到第i-1个结点
    		p = p->next;
    		j++;
    	}
    	return p;
    }
    //按值查找
    Node* LocateElem(LinkList L, int e) {//找到数据域==e的结点
    	Node* p = L->next;				 //p指针指向链表L的结点
    	while (p != NULL && p->data != e)//从第1个结点开始查找数据域e的结点
    		p = p->next;
    	return p;
    }
    
    //求表的长度
    int Length(LinkList L) {
    	int len = 0;//统计表长
    	Node* p = L;//p指针指向链表L的头结点
    	while (p->next != NULL) {//遍历链表统计长度
    		p = p->next;
    		len++;
    	}
    	return len;
    }
    
    单链表建立
    //建立单链表——尾插法(带头结点):得到为插入的正序
    LinkList List_TailInsert(LinkList *L) {//正向建立单链表
    	int x;							  //结点数据域数据类型为整型
    	(*L) = (LinkList)malloc(sizeof(Node));//建立头结点
    	Node* s, * r = (*L);				   //声明s,r指针指向头结点,r为表尾指针
    	scanf("%d", &x);				   //输入结点的值
    	while (x != 408) {				   //输入408表示结束
    		s = (Node*)malloc(sizeof(Node));//将s的指针指向新结点
    		s->data = x;				   //将数据存入新结点中
    		r->next = s;				   //并将存入数据的新结点插入到r结点之后
    		r = s;						   //r指向新的表尾结点
    		scanf("%d", &x);			   //继续输入结点的值
    	}
    	r->next = NULL;					   //尾结点指针置空
    	return (*L);
    }
    //建立单链表——头插法(带头结点):得到为插入的逆序
    LinkList List_HeadInsert(LinkList *L) {//逆向建立单链表
    	Node* s;						  //声明一个s指针
    	int x;							  //结点数据域数据类型为整型
    	(*L) = (LinkList)malloc(sizeof(Node));//建立头结点
    	(*L)->next = NULL;				       //将头结点指向NULL(防止脏数据)
    	scanf("%d", &x);				   //输入结点的值
    	while (x != 408) {				   //输入408表示结束
    		s = (Node*)malloc(sizeof(Node));//将s的指针指向新结点
    		s->data = x;				   //将数据存入新结点中
    		s->next = (*L)->next;			   //将新结点的下一个结点指向头结点的下一个结点(NULL)
    		(*L)->next = s;				   //并将头结点的下一个结点指向存入新数据的s结点中
    		scanf("%d", &x);			   //继续输入结点的值
    	}
    	return (*L);
    }
    
    主函数调用
    //单链表的打印(带头结点)
    void printlist(LinkList L) {
    	printf("该链表的内容为:");
    	while (L->next != NULL) {		 //遍历链表L
    		printf("%d ", L->next->data);//打印头结点的下一个结点中的数据
    		L = L->next;				 //将当前结点指向下一个结点
    	}
    	printf("\n");
    }
    //调用
    void main() {
    	int e = 0;				//定义一个为0的数据值
    	LinkList L //声明一个指向单链表的指针并动态分配存储空间
    	InitList(&L);			//初始化一个空表
    	printf("头插法1 2 3:\n");
    	L = List_HeadInsert(&L);		//头插法建立链表1 2 3 (408结束)
    	printlist(L);			//打印链表
    	printf("当前链表长度:%d\n", Length(L));//打印当前链表长度
    	printf("尾插法1 2 3:\n");
    	L = List_TailInsert(&L);		//尾插法建立链表1 2 3 (408结束)
    	printlist(L);			//打印链表
    	printf("当前链表长度:%d\n", Length(L));//打印当前链表长度
    	printf("在链表第2位处插入4:\n");
    	ListInsert(&L, 2, 4);	//在链表第2位处插入4
    	printlist(L);			//打印链表
    	printf("当前链表长度:%d\n", Length(L));//打印当前链表长度
    	printf("删除链表第1位处的数据值:\n");
    	ListDelete(&L, 1, e);	//删除链表第1位处的数据值并将删除值赋值给e
    	printlist(L);			//打印链表
    	printf("当前链表长度:%d\n", Length(L));//打印当前链表长度
    
    }
    

2.4 双链表

01、初始化
  • 头结点的prior、next都指向NULL
//初始化双链表
bool InitDLinkList(DLinkList* L) {
	(*L) = (DNode*)malloc(sizeof(DNode));	//设置头结点并分配内存空间
	if ((*L) == NULL)						//结点不能为NULL
		return false;
	(*L)->prior = NULL;						//头结点的前指针设永远指向NULL
	(*L)->next = NULL;						//后指针暂时设置为NULL
	return true;
}
02、插入(后插)
  • 注意新插入结点、前驱结点、后续结点的指针修改
  • 边界情况:新插入结点在最后一个位置,需特殊处理
//双链表的插入(后插)
bool InsertDLinkList(DNode* p, DNode* s) {  //将结点s插入到p结点之后
	if (p == NULL || s == NULL)				//结点不能为空
		return false;
	s->next = p->next;						//将结点s插入到p结点之后
	if (p->next != NULL)					//如果p结点有后继结点
		p->next->prior = s;					//p结点的下一个结点的前指针指向s结点
	s->prior = p;							//s结点的前指针指向p结点
	p->next = s;							//p结点的后指针指向s
	return true;
}
03、删除(后删)
  • 注意删除结点的前驱结点、后续结点的指针修改
  • 边界情况:如果被删除结点是最后一个数据结点,需特殊处理
//双链表删除后面结点
bool DeleteDLinkList(DNode* p) {			//删除p结点的后继结点
	if (p == NULL)							//p结点不能为NULL
		return false;
	DNode* q = p->next;						//q结点为p结点的下一个结点
	if (q == NULL)							//q结点不能为NULL(p->next不能为空)
		return false;
	p->next = q->next;						//p结点下一个结点指向q结点的下一个结点
	if (q->next != NULL)					//如果p结点有后继结点
		q->next->prior = p;					//q结点的下一个结点的前指针指向p结点
	free(q);								//释放q结点
	return true;
}
04、遍历
  • 从一个给定结点开始,后向遍历、前向遍历的实现(循环的终止条件)
  • 链表不具备随机存取特性,查找操作只能通过顺序遍历实现
//向后遍历
void Lastloop(DLinkList L) {				//正序
	DNode* p = L->next;						//定义一个p结点为头节点的下一个结点(第一个存放链表元素的结点)
	while (p != NULL) {						//遍历p结点直至为NULL
		printf("%d ", p->data);				//打印当前结点中的数据
		p = p->next;						//将p移动到下一个结点
	}
	printf("\n");
}
//向前遍历
void Preloop(DLinkList L) {					//逆序
	DNode* p = L->next;						//定义一个p结点为头节点的下一个结点(第一个存放链表元素的结点)
	while (p->next != NULL) {				//遍历找到链表的最后一个结点
		p = p->next;
	}
	while (p->prior != NULL) {				//遍历链表直至前结点为空
		printf("%d ", p->data);				//打印当前结点中的数据
		p = p->prior;						//将p结点移动到前一个结点
	}
	printf("\n");
}
05、全部
//双链表的实现方式——带头结点
#include<stdio.h>
#include<stdbool.h>												//C语言中没有布尔类型
#include<stdlib.h>												//malloc、free函数的头文件
//定义双链表
typedef struct DNode {
	int data;								//链表结点数据域
	struct DNode* prior, * next;			//为链表结点设置前指针prior,和尾指针next
}DNode, *DLinkList;
//初始化双链表
bool InitDLinkList(DLinkList* L) {
	(*L) = (DNode*)malloc(sizeof(DNode));	//设置头结点并分配内存空间
	if ((*L) == NULL)						//结点不能为NULL
		return false;
	(*L)->prior = NULL;						//头结点的前指针设永远指向NULL
	(*L)->next = NULL;						//后指针暂时设置为NULL
	return true;
}
//双链表长度
int len(DLinkList L) {
	int len = 0;
	while (L->next != NULL) {				//正向遍历
		L = L->next;
		len++;
	}

	return len;
}
//双链表的插入(后插)
bool InsertDLinkList(DNode* p, DNode* s) {  //将结点s插入到p结点之后
	if (p == NULL || s == NULL)				//结点不能为空
		return false;
	s->next = p->next;						//将结点s插入到p结点之后
	if (p->next != NULL)					//如果p结点有后继结点
		p->next->prior = s;					//p结点的下一个结点的前指针指向s结点
	s->prior = p;							//s结点的前指针指向p结点
	p->next = s;							//p结点的后指针指向s
	return true;
}
//双链表删除后面结点
bool DeleteDLinkList(DNode* p) {			//删除p结点的后继结点
	if (p == NULL)							//p结点不能为NULL
		return false;
	DNode* q = p->next;						//q结点为p结点的下一个结点
	if (q == NULL)							//q结点不能为NULL(p->next不能为空)
		return false;
	p->next = q->next;						//p结点下一个结点指向q结点的下一个结点
	if (q->next != NULL)					//如果p结点有后继结点
		q->next->prior = p;					//q结点的下一个结点的前指针指向p结点
	free(q);								//释放q结点
	return true;
}
//销毁双链表
void DestoryDList(DLinkList* L) {
	while ((*L)->next != NULL)  			//从头结点开始遍历L链表的结点
		DeleteDLinkList((*L));				//每次删除链表中的一个结点
	free((*L));								//释放L链表
	(*L) = NULL;							//将链表置为空
}
//向后遍历
void Lastloop(DLinkList L) {				//正序
	DNode* p = L->next;						//定义一个p结点为头节点的下一个结点(第一个存放链表元素的结点)
	while (p != NULL) {						//遍历p结点直至为NULL
		printf("%d ", p->data);				//打印当前结点中的数据
		p = p->next;						//将p移动到下一个结点
	}
	printf("\n");
}
//向前遍历
void Preloop(DLinkList L) {					//逆序
	DNode* p = L->next;						//定义一个p结点为头节点的下一个结点(第一个存放链表元素的结点)
	while (p->next != NULL) {				//遍历找到链表的最后一个结点
		p = p->next;
	}
	while (p->prior != NULL) {				//遍历链表直至前结点为空
		printf("%d ", p->data);				//打印当前结点中的数据
		p = p->prior;						//将p结点移动到前一个结点
	}
	printf("\n");
}
//主函数
void main() {
	DLinkList L;								//定义一个双链表L
	InitDLinkList(&L);							//初始化双链表L
	DNode* s = (DNode*)malloc(sizeof(DNode));	//定义一个s结点
	DNode* r = (DNode*)malloc(sizeof(DNode));	//定义一个r结点
	DNode* w = (DNode*)malloc(sizeof(DNode));	//定义一个w结点
	s->data = 2;								//s结点中的数据设为2
	r->data = 5;								//r结点中的数据设为5
	w->data = 3;								//w结点中的数据设为3
	InsertDLinkList(L, s);						//在初始换的L链表后插入s结点
	InsertDLinkList(s, r);						//在初s结点后插入r结点
	InsertDLinkList(r, w);						//在初r结点后插入s结点
	printf("正向遍历:");
	Lastloop(L);								//正向遍历链表
	printf("逆向遍历:");
	Preloop(L);									//逆向遍历链表
	printf("长度:%d\n",len(L));
	DeleteDLinkList(r);							//删除r结点后面的结点
	printf("删除r结点后面的结点:");
	Lastloop(L);								//正向遍历删除结点后的链表
	printf("长度:%d\n", len(L));
	DestoryDList(&L);							//销毁链表
	if (L == NULL) {							//检查销毁是否成功
		printf("链表为空");
	}
}

2.5 循环链表

主要举例双链表

01、循环单链表

核心:单手抱自己

与单链表区别:

单链表是从一个结点出发只能找到后续的各个结点

循环单链表是从一个结点出发可以找到其他任何一个结点

  • 定义初始化和判空
//循环链表——循环单链表
#include<stdio.h>
#include<stdbool.h>												//C语言中没有布尔类型
#include<stdlib.h>												//malloc、free函数的头文件
//单链表的定义
typedef struct Node {
	int data;							//定义结点int类型的数据域
	struct Node* next;					//指针指向下一个结点
}Node, * LinkList;
//判断循环单链表是否为空——头结点的下一个结点是否还是头结点
bool Empty(LinkList L) {
	if (L->next == L)					//判断头结点是否为自己
		return true;
	else
		return false;
}
//初始化循环单链表
bool InitLinkList(LinkList* L) {
	(*L) = (Node*)malloc(sizeof(Node));
	if (Empty((*L))) {					//头结点不能为空
		return false;
	}
	(*L)->next = (*L);					//头结点指向自己
    return true;
}
02、循环双链表

核心:双手抱自己

与双链表区别:

双链表的表头结点的prior指向NULL;表尾结点的next指向NULL

循环双链表的表头结点的prior指向表尾结点;表尾结点的next指向头结点

  • 定义初始化和判空
//循环链表——循环双链表
#include<stdio.h>
#include<stdbool.h>												//C语言中没有布尔类型
#include<stdlib.h>												//malloc、free函数的头文件
//定义双链表
typedef struct DNode {
	int data;								//链表结点数据域
	struct DNode* prior, * next;			//为链表结点设置前指针prior,和尾指针next
}DNode, * DLinkList;
//判断循环双链表是否为空——头结点的下一个结点是否还是头结点
bool Empty(DLinkList L) {
	if (L->next == L)						//判断头结点是否是自己
		return true;
	else
		return false;
}
//初始化双链表--保证链表的前后结点不为NULL
bool InitDLinkList(DLinkList* L) {
	(*L) = (DNode*)malloc(sizeof(DNode));	//设置头结点并分配内存空间
	if (Empty((*L)))						//结点不能为空
		return false;
	(*L)->prior = (*L);						//头结点的前指针指向头结点
	(*L)->next = (*L);						//后指针指向头结点
	return true;
}
  • 判断结点p是否为循环双链表的表尾结点
//判断结点p是否为循环双链表的表尾结点
void isTail(DLinkList L, DNode* p) {
	if (p->next == L)						//该结点的下一个结点为头结点
		printf("该结点为尾结点!");
	else
		printf("该结点不为尾结点!");
}
  • 正向遍历循环双链表
//正向遍历循环双链表
void Lastloop(DLinkList L) {
	DNode* p = L;							//定义一个p结点为头节点
	while (p->next != L) {					//遍历p结点直至为头节点
		p = p->next;						//将p移动到下一个结点
		printf("%d ", p->data);				//打印当前结点中的数据
	}
	printf("\n");
}
  • 循环双链表的插入和删除
//循环双链表的插入
bool InsertDLinkList(DNode* p, DNode* s) {  //将结点s插入到p结点之后
	s->next = p->next;						//将结点s插入到p结点之后
	p->next->prior = s;						//p结点的下一个结点的前指针指向s结点
	s->prior = p;							//s结点的前指针指向p结点
	p->next = s;							//p结点的后指针指向s
	return true;
}
//循环双链表删除后面结点
bool DeleteDLinkList(DNode* p) {			//删除p结点的后继结点
	if (Empty(p))							//p结点不能为空
		return false;
	DNode* q = p->next;						//q结点为p结点的下一个结点
	p->next = q->next;						//p结点下一个结点指向q结点的下一个结点
	q->next->prior = p;						//q结点的下一个结点的前指针指向p结点
	free(q);								//释放q结点
	return true;
}
  • 调用
//调用
void main() {
	DLinkList L;								//定义一个双链表L
	InitDLinkList(&L);							//初始化为循环双链表L
	if (Empty(L)) {
		printf("双链表为空\n");
	}
	DNode* s = (DNode*)malloc(sizeof(DNode));	//定义一个s结点
	DNode* r = (DNode*)malloc(sizeof(DNode));	//定义一个r结点
	DNode* w = (DNode*)malloc(sizeof(DNode));	//定义一个w结点
	s->data = 2;								//s结点中的数据设为2
	r->data = 5;								//r结点中的数据设为5
	w->data = 3;								//w结点中的数据设为3
	InsertDLinkList(L, s);						//在初始换的L链表后插入s结点
	InsertDLinkList(s, r);						//在初s结点后插入r结点
	InsertDLinkList(r, w);						//在初r结点后插入s结点
	printf("正向遍历:");
	Lastloop(L);								//正向遍历链表
	DeleteDLinkList(r);							//删除r结点后面的结点
	printf("删除r结点后面的结点:");
	Lastloop(L);								//正向遍历删除结点后的链表
	isTail(L, r);								//判断r结点是否是尾结点
}

2.6 静态链表

核心:用数组的方式实现的链表

优点:增、删操作不需要大量移动元素

缺点:不能随机存取,只能从头结点开始依次往后查找;容量固定不变

  • 定义初始化
//静态链表的实现--带头节点
#include<stdio.h>
#include<stdbool.h>												//C语言中没有布尔类型
#include<stdlib.h>												//malloc、free函数的头文件
//定义静态链表
#define MaxSize 10							//静态链表的最大长度
typedef struct {							//静态链表结构类型的定义
	int data;								//存储数据元素
	int next;								//下一个元素的数组下标
}SLinkList[MaxSize];
//初始化静态链表
bool  InitSLinkList(SLinkList L) {			//下标为0的为头结点,下标为-1为存有数据的静态链表结束,下标为-2为”脏数据“的下标
	L[0].next = -1;							//将下标为0的头结点的下一个元素下标-1
	for (int i = 1; i < MaxSize; i++) {
		L[i].next = -2;						//将头结点除外的其他结点的下一个元素的下标置为-2
	}
	r
  • 判空
//判断静态链表结点是否为空--该链表头结点的下一个元素的下标为-1,且其他元素的下标为-2
bool Empyt(SLinkList L) {
	int l = 0;								//用于统计下标为-2的结点个数
	if (L[0].next == -1)					//头结点的下一个元素的下标是否为 - 1
		for (int i = 1; i < MaxSize; i++)	//其他元素的下标是否为-2
			if (L[i].next == -2)
				l++;
	if (l != MaxSize - 1)					//下标个数不等于链表最大长度-1为空
		return false;
	return true;
}
  • 插入
//插入
bool LinkInsert(SLinkList L, int i, int e) {//在静态链表L下标为i处插入e
	if (i<1 || i>MaxSize-1)					//判断i的范围是否有效
		return false;
	int n = 0;								//用于获取当前数据的下标
	int f = L[0].next;						//f为头结点指向一下元素的下标
	while (f != -1) {						//遍历下标直至为-1
		n = f;								//将f赋值给n,便于找到一个元素下标为-1的结点的下标
		f = L[f].next;						//将f等于自身的下一个元素的下标
	}
	L[n].next = i;							//将下标为n的结点的下一个元素下标置为i
	L[i].data = e;							//将下标为i的结点的数据设置为e
	L[i].next = -1;							//将下标为i的结点的下一个元素下标置为-1
	return true;
}
  • 删除
//删除
bool LinkDelete(SLinkList L, int i) {		//删除静态链表下标为i的数据
	if (i<1 || i>MaxSize - 1)				//判断i的范围是否有效
		return false;
	int n = 0;								//用于获取当前数据的下标
	int f = L[0].next;						//f为头结点指向一下元素的下标
	while (f != i) {						//遍历下标直至为i
		n = f;								//将f赋值给n,便于找到一个元素下标为i的结点的下标
		f = L[f].next;						//将f等于自身的下一个元素的下标
	}
	L[n].next = L[i].next;					//将下标为n的结点的下一个元素下标置为需要删除的结点的下一个元素下标
	L[i].next = -2;							//将被删除结点的下标置为-2,表示结点删除
	return true;
}
  • 遍历
//遍历
void Loop(SLinkList L) {
	if (Empyt(L)) {							//判断链表是否为我们定义的空链表
		printf("循环链表为空\n");
		return;
	}
	int f = L[0].next;						//f为头结点指向一下元素的下标
	while (f != -1) {						//遍历下标直至为-1
		printf("%d ", L[f].data);			//打印链表下标的数据
		f = L[f].next;						//将f等于自身的下一个元素的下标
	}
	printf("\n");
}
  • 调用
//调用
void main() {
	SLinkList a;							//定义一个静态链表(实质为结构体数组)
	InitSLinkList(a);						//初始化静态链表
	printf("链表内容为:");
	Loop(a);								//链表为空,只是链表中没有存放对应数据
	LinkInsert(a, 2, 3);					//在a静态链表下标为2处插入数据3
	LinkInsert(a, 1, 4);					//在a静态链表下标为1处插入数据4
	LinkInsert(a, 3, 5);					//在a静态链表下标为3处插入数据5
	printf("链表内容为:");
	Loop(a);								//遍历链表
	LinkDelete(a, 1);						//删除a静态链表下标为1的结点
	printf("删除a静态链表下标为1的结点:\n");
	printf("链表内容为:");
	Loop(a)
}

2.7 顺序表与链表的比较

  • 顺序表

    表厂可估计、查询(搜索)操作较多

  • 链表

    表长难以预估、经常要增加/删除元素

参考:王道和网上各大博主

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值