数据结构与算法

数据结构与算法

数据结构

是一门研究数据之间关系的学科。

主要有两类需要研究的关系:
物理关系:数据在内存中的实际关系。
顺序结构:根据数据之间的相对位置确定关系
链式结构:在数据中添加一个指针域(地址域),用于指向跟它有关系的数据。
逻辑关系:无视物理关系,人为添加的一种关系。
集合:数据同属一个集体,除此之外没有任何关系。
表:数据之间存在一对一关系,如:数组(顺序表),链表(链式表)。
树:数组之间存在一对多关系。
图:数据之间存在多对多关系。
注意:我们常说的数据结构是逻辑关系,而数据结构的存储方式是物理关系。每种逻辑结构采用什么物理结构存储并没有明确规定,通常以代码实现的难度、以及时间、空间复杂度的要求,选择最合适的物理结构存储,也有可能是链式和顺序的混合储存。

总结

学习数据结构的三个关键点:
1、物理结构
2、逻辑结构
3、结构运算

数据结构常有的运算

创建数据结构:create
销毁数据结构:destroy
清除所有元素:clear
遍历数据结构:show、print
从数据结构中删除一个元素:delete
把一个元素插入到数据结构:install
修改数据结构中某个元素:modify
查询数据结构中的元素:query、find
访问其中一个元素:access

功能被限制的表

栈:只有一个端口进入,元素先进后出FILO。
而栈内存正式使用了这种结构管理内存,所以才叫栈内存。

队列:把一个表结构限制成有两个端口,一个端口只能进,另一个端口只能出,先进先出FIFO。
队头 0
队尾 0
队空 队头 == 队尾
队满 队头 == (队尾+1)%cal;
出队 front = (front+1)%cal;
入队 rear = (rear+1)%cal;
队头元素 ptr[front]
队尾元素 (cal+rear-1)%cal

链式表:
元素:由数据域,指针域组成,元素之间使用指针域连接,这种叫链式存储结构。
如果元素中只有一个指针域,且指向下一个元素,这样元素之间就只存在一对一关系,这种结构叫链式表。

树型结构

树的逻辑结构:数据之间存在一对多关系,常用于表示:家族族谱,组织的架构等。

一般普通树先转换成二叉树(子节点最多有两个的树)再进行讨论。
通常把树的数据项成为节点,子节点的数量叫做度。

struct Node
{
    TYPE data;
    struct Node* sub;
    struct Node* head;
}Node;

二叉树的分类:
普通二叉树:最多有两个子节点,一个叫左子树,一个叫右子树,次序不能颠倒。
满二叉树:二叉树只有度为0的节点和度为2的节点,并且度为0的节点都在最底层,则这棵二叉树为满二叉树。
完全二叉树:深度为h的二叉树,除第h层外,其他各层(1~h-1)的节点数都达到最大个数,第h层所有的节点都连续集中在最左边,这就是完全二叉树。

二叉树的性质:

​ 性质1:二叉树的第i层上至多有2^(i-1)(i>=1)个节点。
​ 性质2:深度为h的二叉树中至多含有2^h-1个节点。
​ 性质3:若在任意一棵二叉树中,有n0个叶子节点,有n2个度为2的节点,则必有n0=n2+1。
​ 性质4:具有n个节点的完全二叉树深度为log2x+1(其中x表示不大于n的最大整数)。
​ 性质5:若对一棵有n个节点的完全二叉树进行顺序编号(1≤i≤n),那么,对于编号为i(i≥1)的节点:
​ 当i=1时,该节点为根,它无双亲节点。
​ 当i>1时,该节点的双亲节点的编号为i/2。
​ 若2i≤n,则有编号为2i的左节点,否则没有左节点。
​ 若2i+1≤n,则有编号为2i+1的右节点,否则没有右节点。

二叉树的存储:
顺序存储:先补成完全二叉树,然后利用性质5,对二叉树进行操作。
如果数比较稀疏,会非常浪费内存。
链式存储:一个节点由数据、左子树指针、右子树指针组成。

如何表示一棵二叉树:
字符串:把所有节点的左右子树填充为#,然后从上到下描述一棵树
前序中序后序:
前序:根、左、右
中序:左、根、右
后序:左、右、根
注意:前序+中序或 中序+后序可以还原一棵二叉树。

有序二叉树:
左子树 < 根 <= 右子树
特点:可以动态得插入删除节点。

算法

广义:解决特定问题的方法。
狭义:数据结构的运算。

解决问题的方法和步骤(数据结构所具备的功能)
特征:有穷性、确切性、输入项、输出项、可行性
评定:
时间复杂度:随着输入项的变化,算法运算得次数,一般采用大o表示法。
o(1)<o(logn)<o(n)<o(nlogn)<o(n2)<o(2n)<o(n!)
空间复杂度:随着输入项的变化,算法所需要的内存的变量,一般采用大o表示法。
o(1)<o(n)<o(2n)
正确性
可读性
健壮性

排序算法

冒泡排序

​ 特点:对数据的有序性敏感,一旦排序完成就能停止。
​ 时间复杂度:
​ 最优:o(n)
​ 最差:o(n*2)

void bubble_sort(int* arr,int len)
{
	bool flag = true;
	for(int i=len-1; i>0 && flag; i--)
	{
		flag = false;
		for(int j=0; j<i; j++)
		{
			if(arr[j]>arr[j+1])
			{
				swap(arr[j],arr[j+1]);
				flag = true;
			}
		}
	}
}
选择排序

​ 特点:交换数据的次数比其他算法少。
​ 时间复杂度:o(n^2)

void select_sort(int* arr,int len)
{
	for(int i=0; i<len-1; i++)
	{
		int min = i;
		for(int j=i+1; j<len; j++)
		{
			if(arr[min] > arr[j])
				min = j;
		}
		if(min!=i)
			swap(arr[i],arr[min]);
	}
}
插入排序

​ 特点:把数据分成两份,一部分有序,一部分无序,把无序中的数据逐个往有序部分中插入,适合向有序的数据中添加新的数据。
​ 时间复杂度:o(n^2)

void insert_sort(int* arr,int len)
{
	for(int i=1,j; i<len; i++)
	{
		int num = arr[i];
		for(j=i; j-1>=0&&num<arr[j-1]; j--)
		{
			arr[j] = arr[j-1];
		}
		arr[j] = num;
	}
}
希尔排序

​ 特点:是插入排序的改进版,通过缩减增量来提高排序速度的一种方法。
​ 时间复杂度:o(n1.3)~o(n2)

void shell_sort(int* arr,int len)
{
	for(int k=len/2; k>0; k=k/2)
	{
		for(int i=k,j; i<len; i+=k)
		{
			int num = arr[i];
			for(j=i; j-k>=0 && num<arr[j-k]; j-=k)
			{
				arr[j] = arr[j-k];
			}
			arr[j] = num;
		}
	}
}
快速排序

​ 特点:先确定一个标杆,然后从左边找比标杆大的数与标杆交换,再从右边找比标杆小的数与标杆交换,达到标杆左边的数据都比它小,右边的都比它大,达到大致有序的效果,然后再使用同样的方法排序标杆左边的数据,然后再排序标杆右边的数据,直到排序完成。
​ 时间复杂度:o(nlogn),o(n^2)

void _quick(int* arr,int left,int right)
{
	// 要求待排序的数据>1
	if(left >= right) return;

	// 计算标杆
	int pi = (left+right)/2;
	// 备份标杆的值
	int pv = arr[pi];
	// 备份左右边界
	int l = left,r = right;

	while(l < r)
	{
		// 在标杆的左边寻找比标杆大的值
		while(l<pi && arr[l]<=pv) l++;
		// 如果寻找到则与标杆交换位置
		if(l<pi)
		{
			arr[pi] = arr[l];
			pi = l;
		}

		// 在标杆的右边寻找比标杆大的值
		while(pi<r && arr[r]>=pv) r--;
		// 如果寻找到则与标杆交换位置
		if(pi<r)
		{
			arr[pi] = arr[r];
			pi = r;
		}
	}
	arr[pi] = pv;
	if(pi-left>1) _quick(arr,left,pi-1);
	if(right-pi>1) _quick(arr,pi+1,right);
}

void quick_sort(int* arr,int len)
{
	_quick(arr,0,len-1);
}
堆排序

​ 特点:把待排序的数据看作一个完全二叉树,先构建成再根堆或小根堆,然后把堆顶与最后一个数据交换,再把待排序的数据-1,然后再调整堆,直到排序完成。
​ 时间复杂度:o(nlogn)

// 循环
void heap_for_sort(int* arr,int len)
{
	// 构建堆
	for(int i=len-1; i>0; i--)
	{
		int j = i;
		while(j>=0)
		{
			if(arr[j] > arr[(j+1)/2-1])
			{
				swap(arr[j],arr[(j+1)/2-1]);
			}
			j = (j+1)/2-1;
		}
	}

	for(int i=len-1; i>0; i--)
	{
		swap(arr[0],arr[i]);
		int j = 0;
		while(j<i)
		{
			int l = (j+1)*2-1;
			int r = (j+1)*2;
			if(r<i && arr[l] < arr[r])
			{
				swap(arr[l],arr[r]);
			}
			else if(l<i && arr[j] < arr[l])
			{
				swap(arr[j],arr[l]);
				j = l;
			}
			else
			{
				break;
			}
		}
	}
}
// 递归
void create_heap(int* arr,int i,int len)
{
	if(i >= len)
		return;

	int l = (i+1)*2-1;
	int r = (i+1)*2;
	
	create_heap(arr,l,len);
	create_heap(arr,r,len);
	if(r < len && arr[l] < arr[r])
		swap(arr[l],arr[r]);
	if(l < len && arr[i] < arr[l])
		swap(arr[i],arr[l]);
}

void heap_sort(int* arr,int len)
{
	show_arr("sort befor",arr,len);
	create_heap(arr,0,len);
	// 创建堆
	for(int i=len-1; i>0; i--)
	{
		swap(arr[0],arr[i]);
		create_heap(arr,0,i);
	}
	show_arr(__func__,arr,len);
}
归并排序

​ 把待排序的数据分隔成一个个子结构,然后把这些子结构按从小到大的顺序复制到临时空间,复制完成后再从临时空间拷贝到原空间,这样就排序完成,该算法使用复制过程代替交换过程,以此提高排序速度,是一种典型的使用空间换取时间算法。
​ 时间复杂度:o(nlogn)

void merge(int* arr,int* tmp,int left,int min,int right)
{
	int i = left , l = left , r = min+1;
	while(l<=min && r<=right)
	{
		if(arr[l] < arr[r])
			tmp[i++] = arr[l++];
		else
			tmp[i++] = arr[r++];
	}
	while(l<=min)tmp[i++] = arr[l++];
	while(r<=right) tmp[i++] = arr[r++];
	memcpy(arr+left,tmp+left,(right-left+1)*sizeof(arr[0]));
}

void _merge_sort(int* arr,int* tmp,int left,int right)
{
	if(left >= right)
		return;
	int min = (left+right)/2;
	_merge_sort(arr,tmp,left,min);
	_merge_sort(arr,tmp,min+1,right);
	merge(arr,tmp,left,min,right);
}

void merge_sort(int* arr,int len)
{
	int* tmp = malloc(sizeof(int)*len);
	_merge_sort(arr,tmp,0,len-1);
	free(tmp);
}
计数排序

​ 把待排序的数据中的最大值、最小值,然后创建哈希表,统计每个数据出现的次数,然后按从小到大的顺序访问哈希表,当哈希表中的数据不为零说明该数据现为待排序的数据,然后把它存放到数组中。
​ 缺点:创建哈希表需要额外的内存,数据的最大值、最小值不确定可能效率非常低。(只适合对数据之间差值不打,重复性高的数据进行排序)。
​ 时间复杂度:o(n+k)

void count_sort(int* arr,int len)
{
	int max = arr[0] , min = arr[0];
	for(int i=1; i<len; i++)
	{
		if(arr[i] > max)
			max = arr[i];
		if(arr[i] < min)
			min = arr[i];
	}
	int* cntp = calloc(4,max-min+1);
	for(int i=0; i<len; i++)
	{
		cntp[arr[i]-min]++;
	}
	int index = 0;
	for(int i=min; i<=max; i++)
	{
		for(int j=0; j<cntp[i-min]; j++)
		{
			arr[index++] = i;
		}
	}
}
桶排序

​ 先按照数据的规模创建桶,然后再把数据分散到桶中,然后调用其他排序算法把桶进行排序,然后再把桶中的数据合并到数组中,该算法是通过降低待排序数据的规模来提高排序速度。
​ 缺点:如何分桶,桶定义多大,这些都需要对有数据有大致的了解。
​ 时间复杂度:o(n+k)

基数排序

​ 创建10个队列,把待排序的数据逐个识别个位数据,然后根据识别的结构吧数据存储到10个队列中,然后从队列出一次弹出,然后是十位、百位…重复这一过程,直到所有数据的最高位为0。
​ 缺点:只适合排序正整数据。
​ 时间复杂度:o(n+k)

统计十种排序算法 最优 最差 平均 是否稳定

查找算法

顺序查找

​ 逐个遍历对比,对数据没有要求。
​ 时间复杂度:o(n)

二分查找

​ 要求数据必须有序,根据左右便捷计算出中间值,如果待查找的数据比中间值要小则在中间值的左边继续查,否则在右继续查,重复以上步骤,知道找到或者边界相遇。
​ 时间复杂度:o(longn)

哈希查找

​ 需要先对数据建立哈希表,然后再访问哈希表查询。
​ 时间复杂度:o(1)~o(n)

块查找

​ 需要先对数据进行分块,查找时先找到对应的数据块,再按照顺序或二分查找.
​ 时间复杂度:o(1)~o(n)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值