数据结构与算法

一、概述

数据结构:逻辑结构+存储结构

逻辑结构:
        线性逻辑结构:数据之间的关系是线性的(一对一)

        线性表、栈、队列

        非线性逻辑结构:

        树型结构 (一对多)

        图型结构 (多对多)

存储结构:

        顺序存储:逻辑上相邻的元素,在存储器上的位置也是相邻的。(数组)

        链式存储:逻辑上相邻的元素,在存储器上的位置可以不是相邻的,但还得实现逻辑上的相邻(利用指针)。(链表、指针开二维数组)

        索引存储 

        散列存储(哈希)

二、算法

三、线性表

定义:

具有相同数据类型有限(n)个数据元素(可以由多个元素组成)的序列,n==0时,空表,n>=1时,L=(a1,a2,...an),a1 表头元素,an 表尾元素,除a1以外,其余元素都有唯一的前驱元素,除an以外,其余元素都有唯一的后继元素,形成了一种一对一的关系。

数组的大小>=线性表的大小。

操作:增删查改

存储结构的实现:

顺序表 (内存中以一维数组的形式存储)

顺序存储结构,逻辑上相邻,实际也相邻。

#include<stdio.h>
#include<stdlib.h>
#define my_maxx 100
//顺序表,int,
typedef struct ArrryList {
	int* arry;//指针模拟开一维数组,避免内存的浪费
	int arry_max;//表的最大数量
	int len;//表的当前长度
}Arry;
//初始化数组
Arry initarray() {
	Arry b;
	b.arry = (int*)malloc(sizeof(Arry) * my_maxx);
	if (b.arry == NULL) {
		printf("内存分配失败\n");
	}
	b.arry_max = my_maxx;
	b.len = 0;
	return b;
}
//顺序插入数据temp
void arry_add(Arry* p, int temp) {
	//判断是否超范围
	if (p->len < p->arry_max) {
		p->arry[p->len] = temp;
		p->len++;
	}
	else
	{
		printf("内存已满,插入失败\n");
	}

}
//在第k位插入元素temp
void my_insert(Arry* p, int k, int temp) {
	//判断是否超范围
	if (p->len < p->arry_max) {
		//将k位以后的元素后移,再插入元素temp
		for (int i = p->len - 1; i >= k; i--) {
			p->arry[i+1] = p->arry[i];
		}
		p->arry[k] = temp;
		p->len++;
	}
	else {
		printf("内存已满,插入失败\n");
	}
}

//查找元素,返回索引,失败返回-1
int find(Arry* p, int temp) {
	for (int i = 0; i < p->len; i++) {
		if (temp == p->arry[i]) {
			return i;
		}
	}
	return -1;
}
//删除元素temp
void del(Arry* p, int temp) {
	//查找,返回索引
	int j = find(p, temp);
	if (j == -1) {
		printf("此元素不存在");
		return;
	}
	//找到了
	for (int i = j; i < p->len; i++) {
		//j后面的元素前移一位,覆盖此元素
		p->arry[i] = p->arry[i + 1];
	}
	p->len--;
	return;
}
//把元素temp更改为tem
void my_change(Arry* p, int temp, int tem) {
	int j = find(p, temp);
	if (j == -1) {
		printf("此元素不存在\n");
		return;
	}
	p->arry[j] = tem;
}
void show(Arry a)
{
	for (int i = 0; i < a.len; i++)
	{
		printf("%d ", a.arry[i]);
	}
	printf("\n");
}
int main() {
	Arry a;
	a = initarray();
	arry_add(&a, 0);
	arry_add(&a, 1);
	arry_add(&a, 3);
	show(a);
	my_insert(&a, 2, 2);
	show(a);
	del(&a, 1);
	show(a);
	return 0;
}

优缺点

优点:查找定位元素快(通过数组下标直接访问)。

缺点:

  • 增删操作复杂。
  • 表的大小是确定的,不易扩展。(扩展只能声明更大的空间,然后复制)
  • 由于数据大小的不可预测性,可能会造成空间的浪费。

应用场景:

⼀组地址连续的存储单元依次存储数据元素的线性结构,且不会对数据进行大量改动。

效率分析

顺序表对于插⼊、删除⼀个元素的时间复杂度是O(n)。 因为顺序表⽀持随机访问,顺序表读取⼀个元素的时间复杂度为O(1)。因为我们是通过下标访 问的,所以时间复杂度是固定的,和问题的规模⽆关。 最⼤的优点是空间利⽤率⾼。最⼤的缺点是⼤⼩固定。

链表

链式存储结构:实际可以不相邻,需要指针实现逻辑上的相邻。

单向链表

存储一个数据:数据本身+下一个数据的地址(一个结构体),即一个节点。

头指针:保存第一个节点地址的指针,以头指针命名链表。

首元节点:第一个保存实际数据的节点。

 判断:

  •         头指针指向头节点   ---------错
  •         头指针指向首元节点---------错
  •         都缺少前提

头节点:存储脏数据(可以理解不保存需要的数据)和首元节点的地址,为了使首元节点和其他节点一样(地址存储在另一个节点中),这样在操作时可以“一视同仁”。(比如,如果没有这个节点,在插入数据时,需要判断是不是在首元节点前插入)

#include<stdio.h>
#include<stdlib.h>
//单项链表
//声明节点的结构体,数据+下一个节点的地址,以及头指针(这里不考虑结构体内存对齐,4+8)
typedef struct Node {
	int data;//数据
	struct Node* next;//下一个节点的地址
}Node, * Linklist;
关于Linklist与Node* 
Linklist L1;
Node* N1;
  • L1是一个指向链表首元素的Node类型的指针,以L1的名字代指整个链表。(同理数组,数组名是指向首元素的指针)
  • N1是一个节点。
  • 声明头指针--Linklist,声明节点指针--Node*,增强可读性
增删查改 
#include<stdio.h>
#include<stdlib.h>
typedef struct Node {
	int data;//数据
	struct Node* next;//下一个节点的地址
}Node, * Linklist;
//初始化函数
Node* initLinklist() {
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("内存分配失败\n");
	}
	else
	{
		s->next = NULL;
	}
	return s;
}
//增:申请一个新节点,存放数据,保存后一个节点的地址,把新节点的地址保存到前一个节点的指针域里
//头插
//L的理解
/*L,值传递,需要返回值,*L二级指针,地址传递,不用返回值*/
//但实际上L始终保存的是头节点的地址,一直不会改变,只是后面的东西变了
Linklist HeadInsert(Linklist L, int k) {
	//申请新节点
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("内存申请失败\n");
	}
	else {
		s->data = k;
		s->next = L->next;
		L->next = s;
	}
	return L;
}
//尾插
Linklist Rearinsert(Linklist L, int k) {
	//创建一个指针指向头节点
	Node* p = L;
	//循环,找到尾节点
	while (p->next != NULL) {
		p = p->next;
	}
	//创建新节点
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("内存申请失败\n");
	}
	else {
		s->data = k;
		s->next = NULL;
		p->next = s;
	}
	return L;
}
//查,找不到返回空 
Linklist Find(Linklist L, int k) {
	Node* p = L->next;
	//p不能为空,否则p->data,报错,p!=NULL也保证了找不到目标数据,且&&两侧条件不能调换顺序
	while (p != NULL && p->data != k) {
		p = p->next;
	}
	return p;
}
//中间插入:在x后面插入
Linklist Midinsert(Linklist L,int x, int k) {
	Node* p = Find(L, x);
	if (p == NULL) {
		printf("数据不存在\n");
	}
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("内存申请失败\n");
	}
	else {
		s->data = k;
		s->next = p->next;
		p->next = s;
	}
	return L;
}
//删
//free(pre->next)释放的不是下个节点,而是节点的指针域
Linklist Delnode(Linklist L, int k) {
	//判空
	if (L->next == NULL) {
		printf("空链表,删除失败");
	}
	//找到k所在的节点,和前一个节点
	Node* pre = L;
	Node* p = L->next;
	while (p != NULL && p->data != k) {
		pre = p;
		p = p->next;
	}
	if (p == NULL) {
		printf("数据%d不存在\n",k);
	}
	else {
		pre->next = p->next;
	}
	free(p);
	p = NULL;//防止p成为野指针
	return L;
}
//改:把x改为k
Linklist Change(Linklist L, int x, int k) {
	Node* p = Find(L, x);
	if (p == NULL) {
		printf("数据%d不存在\n",k);
	}
	p->data = k;
	return L;
}
 优缺点

优点:

  • 灵活
  • 操作效率高

缺点: 

  • 查找不方便
  • 不支持存取

双向链表

比起单向链表,多了一个指针域保存前一个节点的地址。

#include<stdio.h>
#include<stdlib.h>
//双向链表
typedef struct Node {
	int data;
	Node* pre;
	Node* next;
}Node,*Linklist;
//初始化函数,两个指针域赋空值
Linklist Initlist( ) {
	Node* p = (Node*)malloc(sizeof(Node));
	if (p == NULL) {printf("内存不足,初始化失败\n");}
	else {
		p->pre = p->next = NULL;
	}
	return p;
}
//增
// 处理原则:不能使链表(单项)断裂
//头增
Linklist HeadInsert(Linklist L, int k) {
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("内存不足,初始化失败\n");
		return L;
	}
	else {
		s->data = k;
		//先添加s,添加后:(下一节点:s->next,上一节点L)
		s->next = L->next;
		s->pre = L;
		L->next = s;
		//空节点插入,可能没有s->next->pre
		if (s->next != NULL) {
			s->next->pre = s;
		}
	}
	return L;
}
//尾增
Linklist RearInsert(Linklist L, int k) {
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("内存不足,初始化失败\n");
		return L;
	}
	else {
		s->data = k;
		Node* p = L;
		while (p->next != NULL) {//找到尾节点
			p = p->next;
		}
		s->next = p->next;
		s->pre = p;
		p->next = s;
	}
	return L;
}

//查
Linklist Find(Linklist L, int des) {
	Node* s = L->next;
	while (s != NULL && s->data != des) {s = s->next;}
	return s;
}

//中间插入
Linklist MidInsert(Linklist L, int des, int k) {
	Node* p = Find(L, des);
	if (p == NULL) {
		printf("没有找到目标\n");
		return L;
	}
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("内存不足,初始化失败\n");
		return L;
	}
	s->data = k;
	s->next = p->next;
	s->pre = p;
	p->next = s;
	if (s->next != NULL) {
		s->next->pre = s;
	}
	return L;
}

//删,前指后,后(如果存在)指前,释放目标并赋空
Linklist Delnode(Linklist L, int k) {
	if (L->next == NULL) {
		printf("空链表\n");
		return L;
	}
	Node* p = Find(L, k);
	if (p == NULL) {
		printf("没有找到目标\n");
		return L;
	}
	//p->next不一定为空
	p->pre->next = p->next;
	if (p->next != NULL) {
		p->next->pre = p->pre;
	}
	free(p);
	p = NULL;
	return L;
}

//改
Linklist Change(Linklist L, int des,int k) {
	Node* p = Find(L, des);
	if (p == NULL) {
		printf("没有找到目标\n");
	}
	Node* s = (Node*)malloc(sizeof(Node));
	if (s == NULL) {
		printf("内存不足,初始化失败\n");
		return L;
	}
	p->data = k;
	return L;
}

循环链表

循环单链表

循环双链表

二叉链表(树)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值