【算法与数据结构】堆排序&&TOP-K问题

请添加图片描述


📝堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

  1. 建堆
    升序:建大堆
    降序:建小堆
  2. 利用堆删除思想来进行排序
    建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 堆排序代码----->升序:建大堆
    堆排序是通过建立一个大顶堆或小顶堆,然后将堆顶元素与末尾元素交换,并重新调整堆结构,这样重复地交换和调整得到有序序列。在升序排序时,我们希望第一个元素是最大的,所以需要建立大顶堆,这样堆顶元素就是当前所有元素中的最大值。
//升序,建大堆
//O(N*logN)

//定义一个交换函数,用于交换两个元素的值
void Swap(int* px, int* py)
{
	int temp = *px;
	*px = *py;
	*py = temp;
}
//将以parent为根节点的子树进行向下调整,使其满足大堆的性质
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1; //左孩子节点的下标
	while (child < n)
	{
		//找到左右孩子节点中较大的一个
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}
		//如果孩子节点的值大于父节点的值,则交换位置
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//堆排序函数
void HeapSort(int* a, int n)
{
	//将数组a直接建堆,使其满足大堆的性质
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	int end = n - 1; //用于记录堆的末尾位置
	while (end > 0)
	{
		//将堆顶元素与末尾元素交换位置,即将最大值放到末尾
		Swap(&a[0], &a[end]);
		//对除了末尾元素外的部分进行向下调整,使其满足大堆的性质
		AdjustDown(a, end, 0);
		end--;
	}

}
int main()
{
	int a[] = { 3,9,5,2,7,8,10,1,4 };
	printf("堆升序前\n");
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	//堆升序,建大堆
	HeapSort(a, sizeof(a) / sizeof(int));
	printf("\n堆升序后\n");
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;

}

代码运行:
在这里插入图片描述

  1. 堆排序代码----->降序:建小堆
    而在降序排序时,我们希望第一个元素是最小的。如果还建立大顶堆,那么堆顶元素会是最大值,这与我们希望的降序结果不符。所以在降序排序时,我们需要建立一个小顶堆。这样堆顶元素就是当前所有元素中的最小值,和我们希望的降序结果一致。通过每次交换堆顶(最小值)和末尾元素,可以实现数组从小到大排列,也就是降序排序结果。
#include <stdio.h>
// 交换两个元素的值
void Swap(int* px, int* py)
{
	int temp = *px;
	*px = *py;
	*py = temp;
}
// 将以parent为根节点的子树调整为小堆
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1; // 左孩子节点的下标
	while (child < n)
	{
		// 找到左右孩子节点中值较小的节点
		if (child + 1 < n && a[child + 1] < a[child])
		{
			child++;
		}
		// 如果子节点的值小于父节点的值,则交换父子节点的值
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
// 堆排序
void HeapSort(int* a, int n)
{
	// 建堆:从最后一个非叶子节点开始,依次向上调整子树为小堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	int end = n - 1; // 堆的最后一个元素的下标
	while (end > 0)
	{
		// 将堆顶元素(最小元素)与堆的最后一个元素交换位置
		Swap(&a[0], &a[end]);
		// 将除了最后一个元素之外的部分重新调整为小堆
		AdjustDown(a, end, 0);
		end--;
	}

}
int main()
{
	int a[] = { 3,9,5,2,7,8,10,1,4 };
	printf("堆降序前\n");
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	// 使用堆排序进行降序排序
	HeapSort(a, sizeof(a) / sizeof(int));
	printf("\n堆降序后\n");
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;

}

在这里插入图片描述

🌠 TOP-K问题

TOP-K问题是数据挖掘和信息检索中的一个重要问题。

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
TOP-K问题是数据挖掘和信息检索中的一个重要问题。

TOP-K问题的含义是:给定一个集合,找出其中值最大或最小的前K个元素。

常见的TOP-K问题有:

  1. 查找文档集合中与查询条件最相关的前K篇文档。这在搜索引擎中很常见。

  2. 从用户评分最高的物品中找出前K个最受欢迎的物品。

  3. 从数据库中找出收入前K高的用户。

  4. 从候选人中找出支持率前K高的候选人,专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。。

TOP-K问题的一般解法包括:

  • 排序法:直接对全集排序,取前K个元素。时间复杂度O(nlogn)
  • 堆排序法:使用小顶堆或大顶堆维护前K个元素,时间复杂度O(nlogk)
  • 选择算法:每次选择当前值最大/小的元素加入结果集,时间复杂度O(nlogk)
  • 空间优化算法:如QuickSelect,找到第K个元素的位置而不是排序全集。
  • 桶排序法:如果值范围有限,可以使用桶排序提升效率。
  • 索引支持的算法:如果有索引支持,可以利用索引更快找出TOP-K,如B+树。

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

  1. 用数据集合中前K个元素来建堆
    前k个最大的元素,则建小堆
    前k个最小的元素,则建大堆
  2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
    将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

🌠造数据

首先我们要TOP-K,那得有数据,先来生成数据,那就生成随机数据到文件。

void CreateNData()
{
	//造数据
	int n = 100000;
	srand(time(0));//使用时间作为随机数种子
	const char* file = "data.txt";//数据文件名
	FILE* fin = fopen(file, "w");//打开文件用于写入
	if (fin == NULL)//检查文件是否打开成功
	{
		perror("fopen error");//输出打开错误信息
		return;
	}

	for (int i = 0; i < n; ++i)//循环写入n行数据
	{
		int x = (rand() + i) % 1000000;//生成0-999999之间的随机数
		fprintf(fin, "%d\n", x);//写入一行数据
	}
	// 别忘了关闭文件哦
	fclose(fin);
}

rand()函数产生的随机数范围是0-RAND_MAX,在C/C++标准库中,rand()范围是0到32767
i的范围是0-9999,因为n定义为10000,所以rand()结果加i范围是:0 + 0 = 0,32767 + 99999 =132,766,没有超过1000000,但取余可以实现随机数更均匀地分布在0-999999范围内
在这里插入图片描述

🌉topk找最大

1、用前10个数据建小堆
2、后续数据跟堆顶数据比较,如果比堆顶数据大,就替代堆顶,进堆
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}


void AdjustDown(int* a, int n, int parent)
{ //a是数组指针,n是数组长度,parent是当前需要下调的父结点索引

	int child = parent * 2 + 1;
	//child表示父结点parent的左孩子结点索引,因为是完全二叉堆,可以通过parent和2计算得到

	while (child < n)
	{
		//如果左孩子存在

		if (child + 1 < n && a[child + 1] < a[child])
		{
			//如果右孩子也存在,并且右孩子值小于左孩子,则child指向右孩子
			child++;
		}
		if (a[child] < a[parent])
			//如果孩子结点值小于父结点值,则需要交换
		{
			Swap(&a[child], &a[parent]);
			//交换孩子和父结点
			parent = child;
			//父结点下移为当前孩子结点

			child = parent * 2 + 1;

			//重新计算新的左孩子结点索引

		}
		else
		{
			break;
		}
	}
}
void topk()
{
	printf("请输入k->");
	int k = 0;
	scanf("%d", &k);
	const char* file = "data.txt";
	//打开文件
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("malloc fail");
		return;
	}
	//临时变量读取文件数据 
	int val = 0;
	//分配内存用于保存最小堆
	int* minheap = (int*)malloc(sizeof(int) * k);
	if(minheap ==NULL)
	{
		perror("malloc fail");
		return;
	}
	//初始化堆,读取文件前k个数据构建最小堆
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);
	}
	
	//建个小堆
	for (int i = (k - 1 - 1) / 2; i >=0; i--)
	{
		AdjustDown(minheap, k, i);
	}

	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		//读取剩余数据,比对顶的值大,就替换他进堆
		if (x > minheap[0])
		{
			//替换堆顶值,并调用下滤调整堆结构
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}
	}

	for (int i = 0; i < k; i++)
	{
	    //输出堆中保存的前k个最大值 
		printf("%d ", minheap[i]);
	}
	printf("\n");
	fclose(fout);

}

int main()
{
	CreateNData();
	topk();
}

输出:
在这里插入图片描述
的确是五个数,怎么验证他是10万个数中最大的那五个数呢?
OK!用记事本打开该文件的data.txt,随机找五个数改大点,比如到百万,再运行,能不能找出这五个数,能就对了。
在这里插入图片描述
再次运行效果图:
在这里插入图片描述


🚩总结

感谢你的收看,如果文章有错误,可以指出,我不胜感激,让我们一起学习交流,如果文章可以给你一个小小帮助,可以给博主点一个小小的赞😘

请添加图片描述

  • 60
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
1. 对一个算法的评价,不包括如下( )方面的内容。 A.健壮性和可读性 B.并行性 C.正确性 D.时空复杂度 2. 在带有头结点的单链表HL中,要向表头插入一个由指针p指向的结点,则执行( )。 A. p->next=HL->next; HL->next=p; B. p->next=HL; HL=p; C. p->next=HL; p=HL; D. HL=p; p->next=HL; 3. 对线性表,在下列哪种情况下应当采用链表表示?( ) A.经常需要随机地存取元素 B.经常需要进行插入和删除操作 C.表中元素需要占据一片连续的存储空间 D.表中元素的个数不变 4. 一个栈的输入序列为1 2 3,则下列序列中不可能是栈的输出序列的是( ) A. 2 3 1 B. 3 2 1 C. 3 1 2 D. 1 2 3 5. 采用开放定址法处理散列表的冲突时,其平均查找长度( )。 A.低于链接法处理冲突 B. 高于链接法处理冲突 C.与链接法处理冲突相同 D.高于二分查找 6. 若需要利用形参直接访问实参时,应将形参变量说明为( )参数。 A.值 B.函数 C.指针 D.引用 7. 在稀疏矩阵的带行指针向量的链接存储中,每个单链表中的结点都具有相同的( )。 A.行号 B.列号 C.元素值 D.非零元素个数 8. 快速排序在最坏情况下的时间复杂度为( )。 A.O(log2n) B.O(nlog2n) C.0(n) D.0(n2) 9. 从二叉搜索树中查找一个元素时,其时间复杂度大致为( )。 A. O(n) B. O(1) C. O(log2n) D. O(n2) 10. 栈和队列的共同特点是( )。 A.只允许在端点处插入和删除元素 B.都是先进后出 C.都是先进先出 D.没有共同点 11. 用链接方式存储的队列,在进行插入运算时( ). A. 仅修改头指针   B. 头、尾指针都要修改 C. 仅修改尾指针 D.头、尾指针可能都要修改 12. 以下数据结构中哪一个是非线性结构?( ) A. 队列    B. 栈 C. 线性表    D. 二叉树 13. 树最适合用来表示( )。 A.有序数据元素 B.无序数据元素 C.元素之间具有分支层次关系的数据 D.元素之间无联系的数据 14. 二叉树的第k层的结点数最多为( ). A.2k-1 B.2K+1 C.2K-1    D. 2k+1 15. 若有18个元素的有序表存放在一维数组A[19]中,第一个元素放A[1]中,现进行二分查找,则查找A[3]的比较序列的下标依次为( ) A. 1,2,3 B. 9,5,2,3 C. 9,5,3 D. 9,4,2,3 16. 对n个记录的文件进行快速排序,所需要的辅助存储空间大致为 A. O(1)   B. O(n)   C. O(1og2n) D. O(n2) 17. 对于线性表(7,34,55,25,64,46,20,10)进行散列存储时,若选用H(K)=K %9作为散列函数,则散列地址为1的元素有( )个, A.1 B.2 C.3 D.4 18. 设有6个结点的无向图,该图至少应有( )条边才能确保是一个连通图。 A.5 B.6 C.7 D.8 19.设数组data[m]作为循环队列SQ的存储空间,front为队头指针,rear为队尾指针,则执行出队操作后其头指针front值为( ) A.front=front+1 B.front=(front+1)%(m-1) C.front=(front-1)%m D.front=(front+1)%m 20.如下陈述中正确的是( ) A.串是一种特殊的线性表 B.串的长度必须大于零 C.串中元素只能是字母 D.空串就是空白串 21.设一组初始记录关键字序列为(345,253,674,924,627),则用基数排序需要进行( )趟的分配和回收才能使得初始关键字序列变成有序序列。 (A) 3 (B) 4 (C) 5 (D) 8 22.设用链表作为栈的存储结构则退栈操作( )。 (A) 必须判别栈是否为满 (B) 必须判别栈是否为空 (C) 判别栈元素的类型 (D) 对栈不作任何判别 23.下列四种排序中( )的空间复杂度最大。 (A) 快速排序 (B) 冒泡排序 (C) 希尔排序 (D) 堆 24.数据的最小单位是( )。 (A) 数据项 (B) 数据类型 (C) 数据元素 (D) 数据变量 25.设一组初始记录关键字序列为(50,40,95,20,15,70,60,45),则以增量d=4的一趟希尔排序结束后前4条记录关键字为( )。 (A) 40,50,20,95 (B) 15,40,60,20 (C) 15,20,40,45 (D) 45,40,15,20 26.设一组初始记录关键字序列为(25,50,15,35,80,85,20,40,36,70),其中含有5个长度为2的有序子表,则用归并排序的方法对该记录关键字序列进行一趟归并后的结果为( )。 (A) 15,25,35,50,20,40,80,85,36,70 (B) 15,25,35,50,80,20,85,40,70,36 (C) 15,25,35,50,80,85,20,36,40,70 (D) 15,25,35,50,80,20,36,40,70,85 27. 设一组权值集合W={2,3,4,5,6},则由该权值集合构造的哈夫曼树中带权路径长度之和为( )。 (A) 20 (B) 30 (C) 40 (D) 45 28.下面程序的时间复杂为( )。 for(i=1,s=0; i<=n; i++) { t=1; for(j=1;j<=i;j++) t=t*j;s=s+t; } A.O(n) B.O(n2) C.O(n3) D.O(n4) 29.递归过程中必定用到的数据结构是( )。 A.循环队列 B.堆栈 C.二叉树 D.散列 30.设按照从上到下、从左到右的顺序从1开始对完全二叉树进行顺序编号,则编号为i结点的左孩子结点的编号为( )。 (A) 2i+1 (B) 2i (C) i/2 (D) 2i-1 31.设一组初始记录关键字序列为(13,18,24,35,47,50,62,83,90,115,134),则利用二分法查找关键字90需要比较的关键字个数为( )。 (A) 1 (B) 2 (C) 3 (D) 4 32.设指针变量top指向当前链式栈的栈顶,则删除栈顶元素的操作序列为( )。 (A) top=top+1; (B) top=top-1; (C) top->next=top; (D) top=top->next; 33. 字符串的长度是指( )。 (A) 串中不同字符的个数 (B) 串中不同字母的个数 (C) 串中所含字符的个数 (D) 串中不同数字的个数 34. 队列是一种( )的线性表。 (A) 先进先出 (B) 先进后出 (C) 只能插入 (D) 只能删除 35.设散列表中有m个存储单元,散列函数H(key)= key % p,则p最好选择( )。 (A) 小于等于m的最大奇数 (B) 小于等于m的最大素数 (C) 小于等于m的最大偶数 (D) 小于等于m的最大合数 36.如下代码段输出的结果是( )。 String s1 = "abc"; String s2 = new String(s1); System.out.println(s1 == s2 + ","); System.out.println(s1.equals(s2)); A.true,true B.false,false C.true,false D.false ,true 37.二叉树有( )种不同的基本形态。 A.3 B.4 C.5 D.6 38.设一组初始记录关键字序列为(Q,H,C,Y,P,A,M,S,R,D,F,X),则按字母升序的第一趟冒泡排序结束后的结果是( )。 (A) F,H,C,D,P,A,M,Q,R,S,Y,X (B) P,A,C,S,Q,D,F,X,R,H,M,Y (C) A,D,C,R,F,Q,M,S,Y,P,H,X (D) H,C,Q,P,A,M,S,R,D,F,X,Y 39.设顺序循环队列Q[0:M-1]的头指针和尾指针分别为F和R,头指针F总是指向队头元素的前一位置,尾指针R总是指向队尾元素的当前位置,则该循环队列中的元素个数为(C )。 (A) R-F (B) F-R (C) (R-F+M)%M (D) (F-R+M)%M 40.设某棵二叉树的中序遍历序列为ABCD,前序遍历序列为CABD,则后序遍历该二叉树得到序列为( )。 (A) BADC (B) BCDA (C) CDAB (D) CBDA 41.设某完全无向图中有n个顶点,则该完全无向图中有( )条边。 (A) n(n-1)/2 (B) n(n-1) (C) n2 (D) n2-1 42.设某棵二叉树中有2000个结点,则该二叉树的最小高度为( )。 (A) 9 (B) 10 (C) 11 (D) 12 43.设一组初始关键字记录关键字为(20,15,14,18,21,36,40,10),则以20为基准记录的一趟快速排序结束后的结果为( A )。 (A) 10,15,14,18,20,36,40,21 (B) 10,15,14,18,20,40,36,21 (C) 10,15,14,20,18,40,36,2l (D) 15,10,14,18,20,36,40,21 44.设一组初始记录关键字序列为(345,253,674,924,627),则用基数排序需要进行()趟的分配和回收才能使得初始关键字序列变成有序序列。 (A) 3 (B) 4 (C) 5 (D) 8 45.设有n个关键字具有相同的Hash函数值,则用线性探测法把这n个关键字映射到Hash表中需要做( )次线性探测。 A.2 B.n(n+1) C.n(n+1)/2 D.n(n-1)/2 46.设一组初始记录关键字序列为(13,18,24,35,47,50,62,83,90,115,134),则利用二分法查找关键字90需要比较的关键字个数为( )。 A.1 B.2 C.3 D.4 47.设二叉树的先序遍历序列和后序遍历序列正好相反,则该二叉树满足的条件是( )。 A.空或只有一个结点 B.高度等于其结点数 C.任一结点无左孩子 D.任一结点无右孩子 48.设有n个待排序的记录关键字,则在堆排序中需要()个辅助记录单元。 (A) 1 (B) n (C) nlog2n (D) n2 49.下面关于线性表的叙述错误的是( )。 (A) 线性表采用顺序存储必须占用一片连续的存储空间 (B) 线性表采用链式存储不必占用一片连续的存储空间 (C) 线性表采用链式存储便于插入和删除操作的实现 (D) 线性表采用顺序存储便于插入和删除操作的实现 50.将10阶对称矩阵压缩存储到一维数组A中,则数组A的长度最少为( )。 (A) 100 (B) 40 (C) 55 (D) 80 51数据结构是指数据及其相互之间的______________。当结点之间存在M对N(M:N)的联系时,称这种结构为_____________________。 52.一组初始记录关键字序列为(Q,H,C,Y,P,A,M,S,R,D,F,X),则按字母升序的第一趟冒泡排序结束后的结果是 。 53.队列的插入操作是在队列的___尾______进行,删除操作是在队列的____首______进行。54.根据搜索方法的不同,图的遍历有 和 两种。 55.堆栈的操作特点是 。 56. 若对一棵完全二叉树从0开始进行结点的编号,并按此编号把它顺序存储到一维数组A中,即编号为0的结点存储到A[0]中。其余类推,则A[ i ]元素的左孩子元素为________,右孩子元素为_______________,双亲元素为____________。 57. 快速排序算法的平均时间复杂度为____________,直接插入排序算法的平均时间复杂度为___________。 58.对于一个长度为n的单链存储的线性表,在表头插入元素的时间复杂度为_________,在表尾插入元素的时间复杂度为____________。 59.一棵高度为5的二叉树中最少含有______个结点,最多含有_____个结点。 11.对于给定的若干个元素,可以构造出的逻辑结构有 结构, 结构和 结构三种。 60.后缀算式9 2 3 +- 10 2 / -的值为__________。中缀算式(3+4X)-2Y/3对应的后缀算式为_______________________________。 61.设完全二叉树的顺序存储结构中存储数据ABCDE,要求给出该二叉树的链式存储结构并给出该二叉树的前序、中序和后序遍历序列。 62.设给定一个权值集合W=(3,5,7,9,11),要求根据给定的权值集合构造一棵哈夫曼树并计算哈夫曼树的带权路径长度WPL。 63.设一组初始记录关键字序列为(19,21,16,5,18,23),要求给出以19为基准的一趟快速排序结果以及第2趟直接选择排序后的结果。 64..线性表的存储结构有哪两种,各有何特点? 65.在顺序循环队列中,什么是假溢出?请指出解决假溢出的常见方法。 66.阅读下面的算法: LinkList mynote(LinkList L) {//L是不带头结点的单链表的头指针 if(L&&L->next){ q=L;L=L->next;p=L; S1: while(p->next) p=p->next; S2: p->next=q;q->next=NULL; } return L; } 语句S1和语句组S2的功能是什么? 67.已知一个无向图的顶点集为{a, b, c, d, e} ,其邻接矩阵如下所示 (1)画出该图的图形; (2)根据邻接矩阵从顶点a出发进行深度优先遍历和广度优先遍历,写出相应的遍历序列。 68.已知关键字序列为{8,17,26,32,40,72,87,99},采用折半查找算法,给定值为70,35时分别与哪些元素比较?画出相应的二叉判定树 69. 设散列表的长度为8,散列函数H(k)=k mod 7,初始记录关键字序列为(25,31,8,27,13,68),要求分别计算出用线性探测法和链地址法作为解决冲突方法的平均查找长度。 70.对于KMP算法,模式串中每个字符的最大真子串构成一个数组,定义为模式串的next[j]函数。next[j]函数定义如下: Max{k|0<k<j且“t0t1…tk-1”=“tj-ktj-k+1…tj-1”} 当此集合非空时 next[j]= 0 其他情况 -1 当j=0 计算t=“abcabaa”的next[j],要求写出具体的推算步骤。
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法与数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。
以下是一套数据结构算法的实现,包含顺序表、单链表、顺序栈、循环队列、二叉树的二叉链表、图的邻接矩阵和邻接表、基本排序算法(直接插入排序、希尔排序、简单选择排序、快速排序、堆排序、归并排序): ```python # 顺序表 class SeqList: def __init__(self, maxsize=None): self.maxsize = maxsize self._items = [None] * self.maxsize self.length = 0 def __getitem__(self, index): if index < self.length: return self._items[index] else: raise IndexError('Index out of range') def __setitem__(self, index, value): if index < self.length: self._items[index] = value else: raise IndexError('Index out of range') def __len__(self): return self.length def __repr__(self): return str(self._items[:self.length]) def insert(self, index, value): if self.length >= self.maxsize: raise Exception('Full') if index < 0: index += self.length if index < 0: index = 0 if index > self.length: index = self.length for i in range(self.length-1, index-1, -1): self._items[i+1] = self._items[i] self._items[index] = value self.length += 1 def delete(self, index): if self.length == 0: raise Exception('Empty') if index < 0: index += self.length if index < 0 or index >= self.length: raise IndexError('Index out of range') for i in range(index, self.length-1): self._items[i] = self._items[i+1] self.length -= 1 # 单链表 class Node: def __init__(self, value=None, next=None): self.value = value self.next = next class LinkedList: def __init__(self): self.head = Node() self.length = 0 def __len__(self): return self.length def __repr__(self): values = [] current_node = self.head.next while current_node: values.append(current_node.value) current_node = current_node.next return '->'.join(str(value) for value in values) def insert(self, index, value): if index < 0: index += self.length if index < 0: index = 0 if index > self.length: index = self.length prev_node = self.head for i in range(index): prev_node = prev_node.next new_node = Node(value) new_node.next = prev_node.next prev_node.next = new_node self.length += 1 def delete(self, index): if index < 0: index += self.length if index < 0 or index >= self.length: raise IndexError('Index out of range') prev_node = self.head for i in range(index): prev_node = prev_node.next current_node = prev_node.next prev_node.next = current_node.next self.length -= 1 # 顺序栈 class SeqStack: def __init__(self, maxsize=None): self.maxsize = maxsize self._items = [None] * self.maxsize self.top = -1 def __len__(self): return self.top + 1 def __repr__(self): return str(self._items[:self.top+1]) def push(self, value): if self.top == self.maxsize - 1: raise Exception('Full') self.top += 1 self._items[self.top] = value def pop(self): if self.top == -1: raise Exception('Empty') value = self._items[self.top] self.top -= 1 return value # 循环队列 class CircularQueue: def __init__(self, maxsize): self.maxsize = maxsize self._items = [None] * self.maxsize self.head = 0 self.tail = 0 def __len__(self): return (self.tail - self.head + self.maxsize) % self.maxsize def __repr__(self): if self.tail >= self.head: return str(self._items[self.head:self.tail]) else: return str(self._items[self.head:] + self._items[:self.tail]) def push(self, value): if (self.tail + 1) % self.maxsize == self.head: raise Exception('Full') self._items[self.tail] = value self.tail = (self.tail + 1) % self.maxsize def pop(self): if self.tail == self.head: raise Exception('Empty') value = self._items[self.head] self.head = (self.head + 1) % self.maxsize return value # 二叉树的二叉链表 class BinTNode: def __init__(self, data, left=None, right=None): self.data = data self.left = left self.right = right class BinTree: def __init__(self): self._root = None def is_empty(self): return self._root is None def root(self): return self._root def leftchild(self, node): return node.left def rightchild(self, node): return node.right def set_root(self, rootnode): self._root = rootnode def set_left(self, node, leftchild): node.left = leftchild def set_right(self, node, rightchild): node.right = rightchild def preorder_elements(self, node): if node is not None: yield node.data yield from self.preorder_elements(node.left) yield from self.preorder_elements(node.right) def inorder_elements(self, node): if node is not None: yield from self.inorder_elements(node.left) yield node.data yield from self.inorder_elements(node.right) def postorder_elements(self, node): if node is not None: yield from self.postorder_elements(node.left) yield from self.postorder_elements(node.right) yield node.data # 图的邻接矩阵 class Graph: def __init__(self, mat, unconn=0): vnum = len(mat) for x in mat: if len(x) != vnum: raise ValueError('Argument for Graph') self._mat = [mat[i][:] for i in range(vnum)] self._unconn = unconn self._vnum = vnum def vertex_num(self): return self._vnum def _invalid(self, v): return 0 > v or v >= self._vnum def add_vertex(self): raise ValueError('Adj-Matrix does not support "add_vertex"') def add_edge(self, vi, vj, val=1): if self._invalid(vi) or self._invalid(vj): raise ValueError(str(vi) + ' or ' + str(vj) + ' is not a valid vertex.') self._mat[vi][vj] = val def get_edge(self, vi, vj): if self._invalid(vi) or self._invalid(vj): raise ValueError(str(vi) + ' or ' + str(vj) + ' is not a valid vertex.') return self._mat[vi][vj] def out_edges(self, vi): if self._invalid(vi): raise ValueError(str(vi) + ' is not a valid vertex.') return self._out_edges(self._mat[vi], self._unconn) @staticmethod def _out_edges(row, unconn): edges = [] for i in range(len(row)): if row[i] != unconn: edges.append((i, row[i])) return edges # 图的邻接表 class GraphAL(Graph): def __init__(self, mat=[], unconn=0): vnum = len(mat) for x in mat: if len(x) != vnum: raise ValueError('Argument for Graph') self._mat = [Graph._out_edges(mat[i], unconn) for i in range(vnum)] self._vnum = vnum self._unconn = unconn def add_vertex(self): self._mat.append([]) self._vnum += 1 return self._vnum - 1 def add_edge(self, vi, vj, val=1): if self._vnum == 0: raise ValueError('Cannot add edge to empty graph.') if self._invalid(vi) or self._invalid(vj): raise ValueError(str(vi) + ' or ' + str(vj) + ' is not a valid vertex.') row = self._mat[vi] i = 0 while i < len(row): if row[i][0] == vj: self._mat[vi][i] = (vj, val) return if row[i][0] > vj: break i += 1 self._mat[vi].insert(i, (vj, val)) def get_edge(self, vi, vj): if self._invalid(vi) or self._invalid(vj): raise ValueError(str(vi) + ' or ' + str(vj) + ' is not a valid vertex.') for i, val in self._mat[vi]: if i == vj: return val return self._unconn def out_edges(self, vi): if self._invalid(vi): raise ValueError(str(vi) + ' is not a valid vertex.') return self._mat[vi] # 直接插入排序 def insert_sort(lst): n = len(lst) for i in range(1, n): value = lst[i] j = i - 1 while j >= 0 and lst[j] > value: lst[j+1] = lst[j] j -= 1 lst[j+1] = value # 希尔排序 def shell_sort(lst): n = len(lst) gap = n // 2 while gap > 0: for i in range(gap, n): value = lst[i] j = i - gap while j >= 0 and lst[j] > value: lst[j+gap] = lst[j] j -= gap lst[j+gap] = value gap //= 2 # 简单选择排序 def select_sort(lst): n = len(lst) for i in range(n-1): min_index = i for j in range(i+1, n): if lst[j] < lst[min_index]: min_index = j if min_index != i: lst[i], lst[min_index] = lst[min_index], lst[i] # 快速排序 def quick_sort(lst): if len(lst) <= 1: return lst pivot = lst[0] left = [x for x in lst[1:] if x < pivot] right = [x for x in lst[1:] if x >= pivot] return quick_sort(left) + [pivot] + quick_sort(right) # 堆排序 def heap_sort(lst): def sift_down(start, end): root = start while True: child = 2 * root + 1 if child > end: break if child + 1 <= end and lst[child] < lst[child+1]: child += 1 if lst[root] < lst[child]: lst[root], lst[child] = lst[child], lst[root] root = child else: break for start in range((len(lst)-2)//2, -1, -1): sift_down(start,

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿森要自信

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值