啊哈算法--代码

一、排序

1、最简单的排序——桶排序

#include <stdio.h>
#include <string.h>

#define LEN(arr) (sizeof(arr)/sizeof(typeof(arr[0])))

/* 计算数组中分数出现的次数,分数在0-20之间
 */
int main()
{
	int a[10] = {2,0,19,4,8,2,16,1,8,7};
	关键是b的大小,要能包含分数区间。把分数当b的下标
	int b[21] = {};
	memset(b, 0, 21);
	
	for(int i = 0; i < 10; i++)
	{
		b[a[i]]++;
	}
	for(int j = 0; j < 21; j++)
		printf("%d %d times\n", j, b[j]);
	
	
	printf("\n");

	return 0;
}

2、冒泡排序

#include <stdio.h>
该宏定义可以自动计算数组长度
#define LEN(arr) (sizeof(arr)/sizeof(typeof(arr[0])))

从小到大排序
int main()
{
	int arr[10] = {2,3,5,4,8,2,9,1,11,7};
	int tmp;
	需要肌肉记忆,len-1
	for(int i = 0; i < LEN(arr) - 1; i++)
	{
		需要肌肉记忆len-1-i
		for(int j = 0; j < LEN(arr) - 1 - i; j++)
		{
			if(arr[j] >= arr[j+1])
			{
				tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}
	for(int i = 0; i < LEN(arr); i++)
		printf("%d ", arr[i]);

	printf("\n");

	return 0;
}

3、最常用的排序——快速排序

#include <stdio.h>
#include <string.h>

#define LEN(arr) (sizeof(arr)/sizeof(typeof(arr[0])))

void quick_sort(int *arr, int left, int right)
{
	int i, j, tmp, t;
	if(left > right)
		return;
		
	保存基准值,放在后面做交换
	tmp = arr[left];
	哨兵位置
	i = left;
	j = right;

	while(i != j)
	{
		一定是右哨兵先循环
		while(arr[j] >= tmp && i < j)
			j--;

		while(arr[i] <= tmp && i < j)
			i++;

		if(i < j)
		{
			两个哨兵停止后交换值
			t = arr[i];
			arr[i] = arr[j];
			arr[j] = t;
		}
	}
	
	基准值放到哨兵“相遇”处
	arr[left] = arr[i];
	arr[i] = tmp;
	哨兵左右两边重复上述操作
	quick_sort(arr ,left, i-1);
	quick_sort(arr, i+1, right);
		
}

	


/* 计算数组中分数出现的次数,分数在0-20之间
 */
int main()
{
	int a[10] = {1,2,3,6,5,4,7,9,8,10};

	quick_sort(a, 0, LEN(a) - 1);
	
	for(int j = 0; j < 10; j++)
		printf("%d \n", a[j]);
	
	
	printf("\n");

	return 0;
}

以a[0],a[9]为left,right哨兵。a[0]是基准
如果右哨兵大于基准,则right--;当右哨兵小于基准时,停止循环
如果左哨兵小于基准,则left++;当左哨兵大于基准时,停止循环

把left和right位置的值交换,下图红色交换,绿色交换。
重复上面,直到左右哨兵相遇,把基准换到i++的位置,如下图的第二行,就是36交换后

再把左右两边重复以上整个操作

在这里插入图片描述

4、买书

对数组,去重并打印出来

快速排序在最耗时情况下时间复杂度才跟冒泡算法一样,所以优先使用快排,不要再冒泡了
桶排序其实比快速排序还快,但是局限多,如负数不能排,会浪费空间
int main()
{
	int a[10] = {20,40,31,67,40,20,89,300,400,15};

	quick_sort(a, 0, LEN(a) - 1);

	去重打印,j要小于9而不是10
	for(int j = 0; j < 9; j++)
	{
		if(a[j] != a[j+1])
			printf("%d ", a[j]);
			
	}
	打印最后一个数	
	printf("%d ", a[9]);

	return 0;
}

二、栈,队列,链表

1、解密QQ号——队列

主要学习队列这种,用首尾指针的思想

要求:把一串数,第一个删除,第二个加到数组尾部,第三个删除,第四个加到数组尾部,循环至只剩两个数,都删除。打印删除的数

QQ加密后转换出来6 1 5 9 4 7 2 8 3

这种用首尾指针操作好的多,普通做法可能就删,插数组,开销很大的。用指针虽然浪费几个空间,但很省时

#include <stdio.h>

int main()
{
	int arr[101] = {6,3,1,7,5,8,9,2,4};
	int head, tail, i;
	head = 0;
	tail = 9;
	
	tail是在最后一个数组元素之后,并不是4
	6,3,1,7,5,8,9,2,4
	^                 ^
	|				  |
	head	         tail

	while (head < tail)
	{
		printf("%d ", arr[head]);
		head++;
		arr[tail] = arr[head];
		tail++;
		head++;
	}
	return 0;

输出:6 1 5 9 4 7 2 8 3
}
在VS中创建的,可以输入10个数进行“解密”
#include <stdio.h>

typedef struct queue
{
	int data[100];
	int head;
	int tail;
}QUEUE, *pQUEUE;

int main()
{
	QUEUE q;
	int i;
	q.head = 0;
	q.tail = 0;
	for (i = 0; i <= 9; i++)
	{
		scanf("%d", &q.data[q.tail]);
		q.tail++;
	}

	while (q.head < q.tail)
	{
		printf("%d ", q.data[q.head]);
		q.head++;
		q.data[q.tail] = q.data[q.head];
		q.tail++;
		q.head++;
	}
	return 0;

}

2、解密回文——栈

本题是确定一个字符串是否是回文数,这里用“栈”的思想,先进后出,先把前一半字符串进
“栈”,再拿后一半字符串跟栈里的比较,栈里的部分是“弹”,出来的

#include <stdio.h>
#include <string.h>

int main()
{
	char s[101];
	char arr[10] = "abcdcba";
	int i, len, mid, next, top;
	
	len = strlen(arr); 
	mid = len/2 - 1;
	top = 0;
	printf("len = %d\n", len);
	printf("mid = %d\n", mid);
	前一半字符串进栈
	for(i = 0; i <= mid; i++)
	{++,s[0]没存数据,top==0用来判断是否弹出全部字符串的
		s[++top] = arr[i];
	}

	if(len % 2 == 0)
		next = mid + 1;
	else
		next = mid + 2;
		
	printf("next = %d\n", next);

	for(i = next; i <= len - 1; i++)
	{
		printf("top = %d\n", top);
		printf("%c == %c ?\n", s[top], arr[i]);
		if(s[top] != arr[i])
			break;
		
		top--;
	}	

	if(top == 0)
		printf("yes\n");
	else
		printf("no\n");

	return 0;
}




3、小猫钓鱼游戏

两人一次出牌,桌面有相同牌的时候则收回这部分。

出牌就是出队,赢牌则把相同部分放到队尾,没赢则入栈

队列和栈的结合

4、链表

在这里插入图片描述
第一次循环:head,p,q都指向首节点
第二次循环:p,q后移,head不变
第二次循环:p,q后移,head不变
注:下面的释放不一定对
必须有p和q两个,p用来指向新分配,此时q留下来指着链表尾,p分配好后,q再指向p,然后q=p。所以qp总是在链表尾。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define LEN(arr) (sizeof(arr)/sizeof(typeof(arr[0])))

typedef struct node
{
	int data;
	struct node *next;
}NODE, *pNODE;

int main()
{
	pNODE head, p, q, t;
	int i, n, a;
	head = NULL;
	
	printf("input 5 numbers\n");
	
	for(i = 1; i <= 5; i++)
	{
		scanf("%d", &a);
		p = (pNODE)malloc(sizeof(NODE));
		p->data = a;
		p->next = NULL;

		if(head == NULL)
			head = p;
		else
			q->next = p;

		q = p;
		printf("q->data = %d,p->data = %d\n", q->data, p->data);
	}
	
	t = head;
	while(t != NULL)
	{
		printf("%d \n",t->data);
		t = t->next;
	}
	free(head);

	return 0;
}


三、暴力枚举

1、奥数

在方框中填入相同的数使等式成立:口3*6528=3口*8256

挨个试,这是最简单最暴力的
int main()
{
	for(int i = 0; i <= 9; i++)
		if( (i * 10 + 3) * 6528 == (30 + i) * 8256 )
			printf(
"%d\n", i);
			
	return 0;
}

2、炸弹人

炸弹人放哪才能一次炸死最多敌人?(炸弹是个十字,可炸砖墙,但不能穿透)
#表示墙,用G表示敌人,用.表示空地,用二维数组存储,判断即可。

实际问题转模型

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

四、万能的搜索

1、不撞南墙不回头——深度优先搜索

输入一个数,如3,输出123所有的排序
123
132
213
231
312
321
输入4,输出1234的所有排序
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int a[10], book[10], n;

void dfs(int step)
{
	int i;
	//step = m + 1 时表示前面n个盒子都放好了
	//n个数字,公有n + 1个盒子
	if(step == n + 1)
	{
		for(i = 1; i <= n; i++)
		{
			printf("%d", a[i]);
		}
		printf("\n");
		return;
	}

	for(i = 1; i <= n; i++)
	{
		if(book[i] == 0)//等于0表明数字还在手上
		{
			a[step] = i;//将i号数字放到第step个盒子里
			book[i] = 1;//等于1表明数字i放到盒子里了

			dfs(step + 1);//放好第step个盒子后放下一个,调用自己
			book[i] = 0;//回收刚才尝试的的数字
		}
	}
	return;

}


int main()
{
	printf(
"input a number in 1-9\n");
	scanf("%d", &n);
	dfs(1);

			
	return 0;
}


原理很简单,以3个数为例
1 2 3放到编号为1234的四个桶,第四个其实没用到,用作判断

先放第一个桶,1号放到1号桶
2号放到2号桶
3号放到3号桶
到第4个桶时,号没了表示放完了,打印
输出:123

回收3号和23号放到2号桶
2号放到3号桶
到第4个桶时,号没了表示放完了,打印
输出:132

后面两个数排完了,该把1号桶里的换换了,换成2213
231

换成3312
321
下图递归调用自己就只是返回了123132return的地方,b2=0,b3=0,表明从桶里拿出来,交换后再排

这里的深度搜索是指,一条道走到底,如123,排好了再把最后两个能动的调换,变为132
1---->2-----3
|---->3---->2

在这里插入图片描述
3.1节的遗留问题
填入1-9的数字使口口口+口口口=口口口,数字不能重复使用,就可以用深度搜索

2、层层递进——广度优先搜索

两种解法见4.2,4.3

下图,从(1,1)点走到终点最短路径,一开始就有两种走法,记录走过的和不能走的,然后这两步再继续走其他的,这叫广度优先,地毯式的

下面这个是一条路走到底,直到终点。到达终点后再从(1,1)到(1,2)这条路再试,这叫深度优先

在这里插入图片描述

五、图的遍历

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

--------------------------深度优先-----------一条路走到黑
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


int e[101][101], book[101], sum, n;

void dfs(int cur)//当前所在顶点编号,一般是从最高点,即1出发
{
	int i;
	printf("cur = %d\n", cur);
	sum++;//访问一个点就加1
	
	if(sum == n)//所有的点都访问完了,退出
		return;

	for(i = 1; i <= n; i++)//从1号点往其他地方去
	{
		if(e[cur][i] == 1 && book[i] == 0)//如果可以到达,并且改点未被访问过就去到这个点
		{
			book[i] = 1;//标记已访问
			dfs(i);//从i点开始往其他点去
		}
	}
	return;

}


int main()
{
	int i, j, m, a, b;
	scanf("%d %d",&n, &m);

	//初始化二维矩阵,就是初始化e
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= n; j++)
			if(i == j)
				e[i][j] = 0;
			else
				e[i][j] = 99999999;//用这个表示正无穷
	}
	//读取顶点之间的边,可到达的边
	for(i = 1; i <= m; i++)
	{
		scanf("%d %d",&a, &b);
		e[a][b] = 1;
		e[b][a] = 1;
	}

	book[1] = 1;
	dfs(1);
			
	return 0;
}


输入:
5 5(表明生成一个5*5的二维数组,见上图)
1 2(表明12是通的,标记为1,同时21也会被标记为1,这是无方向的)
1 3
1 5
2 4
3 5
输出:
cur = 1(当前在1点)
cur = 2
cur = 4
cur = 3
cur = 5

先从122只能到42这条线遍历完了
再到33也只能到53这条线遍历完了
--------------------------广度优先
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


int main()
{
	int i, j, m, n, a, b, cur, e[101][101], book[101] = {0};
	int que[10001], head, tail;
	scanf("%d %d",&n, &m);

	//初始化二维矩阵,就是初始化e
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= n; j++)
			if(i == j)
				e[i][j] = 0;
			else
				e[i][j] = 99999999;//用这个表示正无穷
	}
	//读取顶点之间的边,可到达的边
	for(i = 1; i <= m; i++)
	{
		scanf("%d %d",&a, &b);
		e[a][b] = 1;
		e[b][a] = 1;
		
	}

	//队列初始化
	head = 1;
	tail = 1;

	//从1号顶点出发,把1号顶点加入队列
	que[tail] = 1;
	tail++;
	book[1] = 1;

	//队列不空开始循环
	while(head < tail)
	{
		cur = que[head];//刚开始que[head] = que[tail] = que[1] = 1
		for(i = 1; i <= n; i++)//尝试1可访问的点
		{
			if(e[cur][i] == 1 && book[i] == 0)
			{
				que[tail] = i;//可访问且未被访问过,把点i入队,从1可到的依次为2,3,5
				tail++;
				book[i] = 1;
			}

			if(tail > n)//已经访问完了
			{
				break;
			}
		}
		head++;
		//上面从1点访问到2,3,5后,要分别从2,3,5下手了,所以head要++,跳到第二层的2,3,5开始
	}

	for(i = 1; i < tail; i++)
		printf("%d ", que[i]);
			
	return 0;
}



输入:
5 5
1 2
1 3
1 5
2 4
3 5
输出:
1 2 3 5 4

广度访问的顺序,如下
在这里插入图片描述

如下图,从1点开始访问,先加入队列,1能访问235。分别把235加入队列,看235分别能访
问哪些点,只有2能访问4,队列为尾部加入4

在这里插入图片描述

2、城市地图——图的深度优先遍历

像这种从1号到5号城市,求最短路程的。要注意这是有方向的,只能1到2,不能2到1

最短路程,需要把每条路都尝试,比较最短,所以用深度优先
在这里插入图片描述

3、最少转机——图的广度优先

同样从1号到5号城市,求转机次数最少(假设每段路程都一样),注意这是无向的,即1可到3,3可到1

对于次数问题,当1可到2,3时,从2,3开始寻找路线,当从3找到5时,就可以结束了,因为这是
广度搜索,要求是次数最少,从2找的路线已经转2次了。

如下,竖着看,从1-3-5就可以结束了,从45没必要,同样从1-2-3-5也没必要了
1---2---3
	|---4
	
1---3---5


在这里插入图片描述

总结:广度适合求这种次数问题,就像二叉树中找某个值一样,不要一条路到底,找不到换条路,万一要找的是3,广度优先一下就找到了。深度优先适合路程问题,如下,要到5,深度在1-2-4,1-2-5就找到了,广度优先就慢。

       1
   2      3
 4   5

六、最短路径

1、只有五行的算法

问题:求任意两城市之间的最短路径

由前面可以,可以求两城市之间的最短,现在是任意两个。这种问题被称为”多源最短路径“

同样转成4*4的二维数组

思路:任意两点之间有路径,有的无法到达。数组记录了能到达和无法到达的(无穷大表示无法到达)
当必须经过两个节点,如节点1和2,那1到3就不能直接到达,要用1–>2–>3,然后跟1–>3的路程比较,哪个小用哪个,替换二维数组中的e[1][3]的值。很明显1-->2-->3路程是5小于7。其他同理

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

#include <stdio.h>

int main()
{
	int e[10][10], k, i, j, n, m, t1, t2, t3;
	int inf = 99999999;
	//读取n和m
	scanf("%d %d", &n, &m);

	//初始化,先初始化为inf
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= n; j++)
		{
			if (i == j)
				e[i][j] = 0;
			else
				e[i][j] = inf;
		}
	}

	//读取边,改变上面的初始化inf
	for (i = 1; i <= m; i++)
	{
		scanf("%d %d %d", &t1, &t2, &t3);
		e[t1][t2] = t3;
	}

	//核心算法语句
	for (k = 1; k <= n; k++)//只允许经过k个节点时,当k=2时

		//必须经过2个节点时(即共3个节点):i=1,j=3表示从1到3,有123,143两条路,显然123更近,为5
		//1直接到3为6,超过5,就更新一下e[1][3]=5。e[1][3]是默认两点之间的
		for (i = 1; i <= n; i++)
			for (j = 1; j <= n; j++)
				if (e[i][j] > e[i][k] + e[k][j])
					e[i][j] = e[i][k] + e[k][j];

	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= n; j++)
		{
			printf("%10d", e[i][j]);
		}
		printf("\n");

	}
	return 0;
}

输入和输出
4 8
1 2 2
1 3 6
1 4 4
2 3 3
3 1 7
3 4 1
4 1 5
4 3 12
         0         2         5         4
         9         0         3         4
         6         8         0         1
         5         7        10         0

在这里插入图片描述

2、Dijkstra算法——通过边实现松弛

指定一个点到其余各点的最短路径,也叫做“单源最短路径
在这里插入图片描述
在这里插入图片描述

思路:
a.1点可到23,目前1-21-3最短,记录一下1,12
1)2拓展,可到4,3,目前1-2-4最短,记录4,。前面记录了1-312,现在中转1-2-310更短,记录一下
2)3拓展到5,目前1-3-5最短为17
b.4拓展,可到3,5,61-4最短已经记录了为4,那刚才从1-2-3是最短的,现在从1-2-4-38更短,再记录8
......
每次都记录最短的,发现新的路径就拿出来跟上次最短的比较

#include <stdio.h>
#include <string.h>
#include <stdlib.h>


int main()
{
	int i, j, m, n, t1, t2, t3, u, v, min, e[10][10], book[10], dis[10];
	int inf = 99999999;
	scanf("%d %d",&n, &m);

	//初始化二维矩阵,就是初始化e
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= n; j++)
			if(i == j)
				e[i][j] = 0;
			else
				e[i][j] = inf;//用这个表示正无穷
	}
	//读取顶点之间的边,可到达的边
	for(i = 1; i <= m; i++)
	{
		scanf("%d %d %d",&t1, &t2, &t3);
		e[t1][t2] = t3;
	}

	//初始化dis,这是1号顶点到其余各顶点的初始路程,用dis[i]表示
	for(i = 1; i <= n; i++)
		dis[i] = e[1][i];

	//book数组初始化,book标记1点到其他点的最短距离有没有被确定,0没确定,1确定了
	for(i = 1; i <= n; i++)
		book[i] = 0;
	book[1] = 1;

	//核心算法
	for(i = 1; i <= n -1; i++)
	{
		min = inf;
		for(j = 1; j <= n; j++)
		{
			if(book[j] == 0 && dis[j] < min)
			{
				min = dis[j];
				u = j;
			}
		}
		//上边一个for循环下来,找到了1点到其相邻点的最近点,从图上看就是点2
		//然后找点2相邻的最近点,第一遍循环u=2
		book[u] = 1;
		printf("point %d\n", u);
		//找2到各个点的其他距离
		for(v = 1; v <= n; v++)
		{
			//如果2到其他点的距离小于inf
			if(e[u][v] < inf)
			{
				//如果从1到v点的值大于通过点u(2)中转,则保存较小值,
				//这里1到3显然通过2中转最近,所以保存,同理1到4通过中转也最近,保存一下

				//到u=4时,1到4最近为1-2-4,距离是4,4可到3,比较上次1-3最近的10,发现1+3+4<1+9,再替换
				if(dis[v] > dis[u] + e[u][v])
				{
					dis[v] = dis[u] + e[u][v];
					printf("%d = %d + %d\n", dis[v], dis[u], e[u][v]);
				}
			}
		}
		printf("\n\n");
	}

	for(i = 1; i <= n; i++)
		printf("%d ", dis[i]);
			
	return 0;
}




后边还有解决路径是“负值”的情况及其优化,几种算法的比较,就不再整了,用得到看pdf

七、神奇的树

1、树

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

1、二叉树

满二叉树:对于深度为h且有2^h-1个节点的二叉树
完全二叉树:高度为h,除h层外其他都达到最大节点个数第 h 层从右向左连续缺若干结点,还TM有方向
在这里插入图片描述
在这里插入图片描述

3、堆——神奇的优先队列

所有父结点都比子结点要小(注意:圆圈里面的数是值,圆圈上面的数是这个结点的编号,此规定仅适用于本节)。符合这样特点的完全二叉树我们称为最小堆
反之,如果所有父结点都比子结点要大,这样的完全二叉树称为最大堆
在这里插入图片描述


完全二叉树特性:节点数为n的完全二叉树,最后一个非叶节点的序号是n/2,如上14/2=7

序号为k的父节点,其左子节点序号为2k,右子节点序号为2k+1(对本节标序号有效)

在这里插入图片描述

--------------------------最小堆排序
#include <stdio.h>

int h[101];//用来存放堆的数组
int n; //用来存储堆中元素的个数,也就是堆的大小,也是树的总结点数

//交换函数,用来交换堆中的两个元素的值 p
void swap(int x, int y)
{
	int t;
	t = h[x] ;
	h[x] = h[y] ;
	h[y] = t;
}

//向下调整函数
void siftdown(int i)//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整
{
	int t ,flag = 0;//flag用来标记是否需要继续向下调整
	
	//当i结点有儿子(其实是至少有左儿子)并且有需要继续调整的时候循环就执行
	while( i*2 <= n && flag == 0)
	{
		//首先判断它和左儿子的关系,并用t记录值较小的结点编号
		if(h[i] > h[i*2])
			t = i*2;
		else
			t = i;

		//如果它有右儿子,再对右儿子进行讨论
		if(i*2 + 1 <= n)
		{
			//如果右儿子的值更小,更新较小的结点编号
			if(h[t] > h[i*2 + 1])
				t = i*2 + 1;
		}

		//如果发现最小的结点编号不是自己,说明子结点中有比父结点更小的,交换,更新
		if(t != i)
		{
			swap(t, i);
			i = t;
		}
		else
			flag = 1;//否则说明当前的父结点已经比两个子给点都要小,不需要再进行调整了

	}
}


//建立堆函数
void creat()
{
	int i;
	//从最后一个非叶结点到第1个结点依次进行向上调整
	for(i = n/2; i >=1; i--)
		siftdown(i);

}

//删除最大的元素
int deletemax()
{
	int t;
	t = h[1];	//用一个临时变量记录堆顶点的值
	h[1] = h[n];//将堆的最后一个点赋值到堆顶
	n--;		//堆的元素减少 1
	siftdown(1);//向下调整,每次都成了最小堆,然后返回堆顶的元素,即最小元素
	return t;	//返回之前记录的堆的顶点的最大值

}


int main()
{
	int i, num;
	//读入要排序的数字的个数
	scanf("%d", &num);

	for(i = 1; i <= num; i++)
		scanf("%d", &h[i]);

	n = num;

	creat();

	//删除顶部元素,连续删除n次,其实也就是从大到小把数输出来
	for(i = 1; i <= num; i++)
		printf("%d ", deletemax());
	printf("\n ");
	return 0;
}
输入:
14
99 5 36 7 22 17 46 12 2 19 25 28 1 92
输出:
1 2 5 7 12 17 19 22 25 28 36 46 92 99
----------------------------分割线---------------------

在这里插入图片描述

--------------------------最大堆排序-----
与最小堆排序区别是,deletemax被换成heapsort,打印换成h[i]

#include <stdio.h>

int h[101];//用来存放堆的数组
int n; //用来存储堆中元素的个数,也就是堆的大小,也是树的总结点数

//交换函数,用来交换堆中的两个元素的值 p
void swap(int x, int y)
{
	int t;
	t = h[x] ;
	h[x] = h[y] ;
	h[y] = t;
}

//向下调整函数
void siftdown(int i)//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整
{
	int t ,flag = 0;//flag用来标记是否需要继续向下调整
	
	//当i结点有儿子(其实是至少有左儿子)并且有需要继续调整的时候循环就执行
	while( i*2 <= n && flag == 0)
	{
		//首先判断它和左儿子的关系,并用t记录值较小的结点编号
		if(h[i] > h[i*2])
			t = i*2;
		else
			t = i;

		//如果它有右儿子,再对右儿子进行讨论
		if(i*2 + 1 <= n)
		{
			//如果右儿子的值更小,更新较小的结点编号
			if(h[t] > h[i*2 + 1])
				t = i*2 + 1;
		}

		//如果发现最小的结点编号不是自己,说明子结点中有比父结点更小的,交换,更新
		if(t != i)
		{
			swap(t, i);
			i = t;
		}
		else
			flag = 1;//否则说明当前的父结点已经比两个子给点都要小,不需要再进行调整了

	}
}


//建立堆函数
void creat()
{
	int i;
	//从最后一个非叶结点到第1个结点依次进行向上调整
	for(i = n/2; i >=1; i--)
		siftdown(i);

}


//堆排序
void heapsort()
{
	while(n > 1)
	{
		swap(1, n);
		n--;
		siftdown(1);
	}
}

//删除最大的元素
int deletemax()
{
	int t;
	t = h[1];	//用一个临时变量记录堆顶点的值
	h[1] = h[n];//将堆的最后一个点赋值到堆顶
	n--;		//堆的元素减少 1
	siftdown(1);//向下调整,每次都成了最小堆,然后返回堆顶的元素,即最小元素
	return t;	//返回之前记录的堆的顶点的最大值

}


int main()
{
	int i, num;
	//读入要排序的数字的个数
	scanf("%d", &num);

	for(i = 1; i <= num; i++)
		scanf("%d", &h[i]);

	n = num;

	creat();

	heapsort();

	//删除顶部元素,连续删除n次,其实也就是从大到小把数输出来
	for(i = 1; i <= num; i++)
		printf("%d ", h[i]);

	printf("\n ");

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值