数据结构知识点①

什么是数据结构?

数据结构 (Data Structure) 是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的
数据元素的集合。通俗地来说,在写代码的时候,需要把一些数据有序的组织起来、存储起来,不同的处理需要不同的结构存储,其实就是在内存当中存储数据。

什么是算法?

算法 (Algorithm): 就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。

时间复杂度和空间复杂度

算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率波称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间,在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

时间复杂度

在计算机科学中,算法的时间复杂度是一个函数,它定是描述了该算法的运行时间。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复度。

大O的渐进表示法

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要 大概执行次数。例如在下面的程序代码中,程序一共执行了:N^2+2N+10次。
void Func1(int N)
{
	int count = 0;
	for (int i = 0; i < N; ++i)
	{
		for (int j = 0; j < N; ++j)
		{
			++count;
		}
	}

	for (int k = 0; k < 2 * N; ++k)
	{
		++count;
	}
	int M = 10;
	while (M--)
	{
		++count;
	}
printf("%d\n", count);
}
O 符号( Big O notation ):是用于描述函数渐进行为的数学符号。
推导大 O 阶方法:
1 、用常数 1 取代运行时间中的所有加法常数。
2 、在修改后的运行次数函数中,只保留最高阶项。
3 、如果最高阶项存在且不是 1 ,则去除与这个项目相乘的常数。得到的结果就是大 O 阶。
另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数 ( 上界 )
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数 ( 下界 )
例如:在一个长度为 N 数组中搜索一个数据 x
最好情况: 1 次找到
最坏情况: N 次找到
平均情况: N/2 次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为 O(N)。

空间复杂度

空间复杂度不去计算占用多少字节,而是去计算大概定义的变量个数。空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度  空间复杂度计算规则基本跟实践复杂度类似,也使用O渐进表示法。 注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是0(1)。

顺序表和链表

线性表

线性表 (inear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串....线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的线性表在物理上存储时,通常以数组和链式结构的形式存储。线性表在逻辑上是连续的,在物理结构上不一定是连续的。

前一个空间存有下一个空间的地址,通过这种方式将各个空间块连接起来,以此可以实现按需申请内存。

顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。顺序表可以理解为一个可动态增长的数组,并且其数据在数组中存储时必须是连续的。优点是可以随机访问;并且缓存利用率比较高。缺点是在进行中间或者头部的插入删除时很慢,需要挪动数据,时间复杂度是O(N);在空间不够时,增容会有一定的消耗和空间上的浪费。顺序表一般分为两类:静态顺序表和动态顺序表。

静态顺序表

静态顺序表使用定长数组存储,顺序表中的有效数据在数组中必须是连续的。静态顺序表无法实现按需所取,他的大小是固定的。

动态顺序表

动态顺序表与静态顺序表不同,动态顺序表使用动态开辟的数组存储。

一个顺序表的例子程序,包括完成顺序表的创建,进行各项数据操作等:

1、头文件.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* a;
	int size;//有效数据的个数
	int capacity;//容量
}SL, SeqList;

void SeqListInit(SL* ps);//初始化
void SeqListDestory(SL* ps);//销毁
void SeqListPrint(SL* ps);//打印
void SeqListCheckCapacity(SL* ps);//检查顺序表的容量并进行扩容
void SeqListPushBack(SL* ps, SLDataType x);//尾插
void SeqListPopBack(SL* ps);//尾删
void SeqListPushFront(SL* ps, SLDataType x);//头插
void SeqListPopFront(SL* ps);//头删
void SeqListInsert(SL* ps, int pos, SLDataType x);//在任意位置插入
void SeqListErase(SL* ps, int pos);//在任意位置删除
int SeqListFind(SL* ps, SLDataType x);//查找

2、源文件.c

#include"SeqList.h"


//初始化
void SeqListInit(SL* ps)
{
	/*s.size = 0;
	s.a = NULL;
	s.capacity = 0;*/
	ps->a = (SLDataType*)malloc(sizeof(SLDataType) * 4);
	if (ps->a == NULL)
	{
		printf("申请内存失败\n");
		exit(-1);
	}
	ps->size = 0;
	ps->capacity = 4;
}

void SeqListDestory(SL* ps)
{
	free(ps->a);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

//打印
void SeqListPrint(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	/*printf("\n");*/
}
//检查容量
void SeqListCheckCapacity(SL* ps)
{
	//如果分配的空间满了,则需要增容
	if (ps->size >= ps->capacity)
	{
		ps->capacity *= 2;
		ps->a = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity);
		if (ps->a == NULL)
		{
			printf("扩容失败\n");
			exit(-1);
		}
	}
}
//尾插、尾删、头插、头删
//尾插
void SeqListPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	SeqListCheckCapacity(ps);
	ps->a[ps->size] = x;
	ps->size++;

}
//尾删
void SeqListPopBack(SL* ps)
{
	assert(ps);
	ps->a[ps->size - 1] = 0;
	ps->size--;
}
//头插
void SeqListPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SeqListCheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[0] = x;
	ps->size++;
}
//头删
void SeqListPopFront(SL* ps)
{
	assert(ps);

	int start = 0;
	while (start < ps->size - 1)
	{
		ps->a[start] = ps->a[start + 1];
		++start;
	}
	ps->size--;
}
//在任意位置的插入
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos < ps->size && pos >= 0);
	SeqListCheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[pos] = x;
	ps->size++;
}
//在任意位置的删除
void SeqListErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos < ps->size && pos >= 0);
	while (pos < ps->size - 1)
	{
		ps->a[pos] = ps->a[pos + 1];
		++pos;
	}
	ps->size--;
}
//查找
int SeqListFind(SL* ps, SLDataType x)
{
	assert(ps);
	int i = 0;
	while (i < ps->size)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
		++i;
	}
	return -1;
}

3、测试文件test

#include"SeqList.h"

//测试程序
void TestSeqList1()
{
	SeqList s;
	SeqListInit(&s);

	SeqListPushBack(&s, 1);
	SeqListPushBack(&s, 2);
	SeqListPushBack(&s, 3);
	SeqListPushBack(&s, 4);
	SeqListPushBack(&s, 5);
	SeqListPushBack(&s, 6);
	SeqListPushBack(&s, 7);
	SeqListPrint(&s);
	printf("尾插结束\n");
	SeqListPopBack(&s);
	SeqListPopBack(&s);
	SeqListPrint(&s);
	printf("尾删结束\n");
	SeqListPushFront(&s,-1);
	SeqListPushFront(&s, -2);
	SeqListPrint(&s);
	printf("头插结束\n");
	SeqListPopFront(&s);
	SeqListPopFront(&s);
	SeqListPrint(&s);
	printf("头删结束\n");
	SeqListErase(&s, 2);
	SeqListPrint(&s);
	printf("在任意位置删除结束\n");
	SeqListInsert(&s, 1, 9);
	SeqListPrint(&s);
	printf("在任意位置插入结束\n");

	int pos = SeqListFind(&s, 4);
	if (pos != -1)
	{
		printf("找到4了!\n");
		SeqListErase(&s,pos);
	}
	SeqListPrint(&s);
	printf("把4找到并删除\n");

	SeqListDestory(&s);
}
int main()
{
	TestSeqList1();
	return 0;
}

链表

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表其实就是针对顺序表的缺点进行设计的,改善了顺序表中的弊端。链表按结构分类有以下几种:1、单向或者双向2、带头或者不带头3、循环或者非循环。

在实际应用中,常用的链表结构为无头单向非循环链表和带头双向循环链表。

无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。

单链表的示例程序:

1、头文件.h

#pragma once
#include <stdio.h>
#include <stdlib.h>

typedef int SListDataType;
//节点
typedef struct SListNode
{
	SListDataType data;
	struct SListNode* next;
}SListNode;

void SListPushBack(SListNode** pphead, SListDataType x);//尾插
void SListPopBack(SListNode** pphead);//尾删
void SListPushFront(SListNode* phead, SListDataType x);//头插
void SListPopFront(SListNode* phead);//头删
void SListPrint(SListNode* phead);//遍历打印

2、源文件.c

#include "SList.h"

//遍历链表
void SListPrint(SListNode* phead)
{
	SListNode* cur = phead;
	while (cur!=NULL)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
}

//申请新的节点
SListNode* BuySListNode(SListDataType x)
{
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
	if (newNode == NULL)
	{
		printf("申请节点失败\n");
		exit(-1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}

//尾插
//先建立一个新的节点,如果原链表是空的,就把头指针指向新节点。如果不是空的,将头指针用tail保存,判断tail后的next是不是空的,
//只要不是空的,就把tail的next赋给tail,直到最后把新建的newNode赋给tail的next,
//相当于在链表最后一个节点存了newNode的地址,实现尾插
void SListPushBack(SListNode** pphead, SListDataType x)
{
	SListNode* newNode = BuySListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newNode;
	}
}

//尾删
//先判断原来的的链表有多少节点,没有节点直接返回,有一个节点的把当前头指针的next置为空,然后把原先的头指针指向的节点free掉
//如果有多个节点,设置prev为空,将当前头指针指向tail,当tail不为空时,将tail赋给prev,并将tail的next赋给tail,
//这样就把tail移到了最后一个节点上,prev移到了倒数第二个节点上,把tail指向的节点free掉,把prev的next置空,实现尾删。
void SListPopBack(SListNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead == NULL;
	}
	else
	{
		SListNode* prev = NULL;
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;
	}
}

//头插
//先判断链表是不是空的,空的话直接把头指针指向新建的节点就行了。
//如果不是空的,则把当前的头指针赋给新节点的next,然后把头指针指向新的节点。
void SListPushFront(SListNode** pphead, SListDataType x)
{
	SListNode* newNode = BuySListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		newNode->next = *pphead;
		*pphead = newNode;
	}
}

//头删
//先判断链表是不是空的,空的直接返回。
//如果不是空的,先用一个tmp把当前的头指针存储起来,然后把当前的头指针的next赋给头指针,再把原先的tmp里面的头指针free掉
void SListPopFront(SListNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else
	{
		SListNode* temp = *pphead;

		*pphead = (*pphead)->next;

		free(temp);
	}
}

3、测试文件.c

#include "SList.h"

int main()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPrint(pList);
	printf("链表尾插结束\n");
	SListPopBack(&pList);
	SListPopBack(&pList);
	SListPopBack(&pList);
	SListPrint(pList);
	printf("链表尾删结束\n");
	SListPushFront(&pList, 5);
	SListPushFront(&pList, 7);
	SListPrint(pList);
	printf("链表头插结束\n");
	SListPopFront(&pList);
	SListPrint(pList);
	printf("链表头删结束\n");
	return 0;
}
双向链表

双向链表相较于单链表的结构,结构更加复杂,但是操作起来更加简单。

链表和顺序表(数组)的区别和联系:  顺序表就是在数组的基础上实现增删查改,并且插入时可以实现动态增长,但是顺序表会可能存在一定的空间浪费,并且在增容时有一定的效率损失,同时中间或者头部的插入删除时要挪动数据,其时间复杂度为O(N)。链表就解决了这些问题。但是链表不能随机访问。

双向链表示例程序:

1、头文件.h

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>


typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}ListNode;

void ListInit(ListNode** pphead);
void ListClear(ListNode* phead);
void ListDestory(ListNode** pphead);
ListNode* BuyListNode(LTDataType x);
void ListPrint(ListNode* phead);
void ListPushBack(ListNode* phead, LTDataType x);
void ListPopBack(ListNode* phead);
void ListPushFront(ListNode* phead, LTDataType x);
void ListPopFront(ListNode* phead);
ListNode* ListFind(ListNode* phead, LTDataType x);
void ListInsert(ListNode* pos, LTDataType x);
void ListErase(ListNode* pos);

2、源文件.c

#include "List.h"


ListNode* BuyListNode(LTDataType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	node->next = NULL;
	node->prev = NULL;
	node->data = x;
	return node;
}

void ListPrint(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
}

void ListInit(ListNode** pphead)
{
	(*pphead) = BuyListNode(0);
	(*pphead)->next = *pphead;
	(*pphead)->prev = *pphead;
}

void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* newnode = BuyListNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* tail = phead->prev;
	phead->prev = tail->prev;
	tail->prev->next = phead;
	free(tail);
}

void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	ListNode* newnode = BuyListNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = cur;
	cur->prev = newnode;
}

void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* cur = phead->next;
	phead->next = cur->next;
	cur->next->prev = phead;
	free(cur);
}

ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	printf("查找失败");
	return NULL;
}

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* cur = pos->prev;
	ListNode* newnode = BuyListNode(x);
	cur->next = newnode;
	newnode->prev = cur;
	newnode->next = pos;
	pos->prev = newnode;

}

void ListErase(ListNode* pos)
{
	assert(pos);

	ListNode* curf = pos->prev;
	ListNode* curb = pos->next;
	free(pos);
	curf->next = curb;
	curb->prev = curf;
}

void ListClear(ListNode* phead)//清理数据节点,保留头节点
{
	assert(phead);
	ListNode* cur = phead;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(next);
		cur = next;
	}
	phead->next = phead;
	phead->prev = phead;
}

void ListDestory(ListNode** pphead)
{
	assert(*pphead);
	ListClear(*pphead);
	free(*pphead);
	*pphead = NULL;
}

3、测试文件.c

#include"List.h"

void TestList1()
{
	ListNode* phead = NULL;
	ListInit(&phead);
	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPushBack(phead, 4);
	ListPrint(phead);
	printf("尾插结束\n");
	ListPopBack(phead);
	ListPrint(phead);
	printf("尾删结束\n");
	ListPushFront(phead, 7);
	ListPushFront(phead, 3);
	ListPrint(phead);
	printf("头插结束\n");
	ListPopFront(phead);
	ListPrint(phead);
	printf("头删结束\n");
	ListNode* pos = ListFind(phead, 3);
	ListInsert(pos,38);
	ListPrint(phead);
	printf("在3的前面插入38结束\n");
	ListNode* pos2 = ListFind(phead, 2);
	ListErase(pos2);
	ListPrint(phead);
	printf("找到2并将其删除\n");

	ListDestory(&phead);
}

int main()
{
	TestList1();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值