数据结构知识点②

栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出的原则。(即后插入的数据先被拿出)。数组、单链表、双向链表都可以实现栈结构。栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。栈的删除操作叫做出栈。出数据也在栈顶。

栈的应用:1、在有后进先出需求的地方,比如迷宫问题。2、将递归改成非递归。

利用数组结构构造一个栈,代码举例:

1、头文件.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int STDataType;

typedef struct Stack
{
	STDataType* _a;
	int _top;
	int _capacity;

}Stack;

void StackInit(Stack* pst);//初始化
void StackDestory(Stack* pst);//销毁
void StackPush(Stack* pst, STDataType x);//入栈
void StackPop(Stack* pst);//出栈
int StackSize(Stack* pst);//获取数据个数
int StackEmpty(Stack* pst);//返回1是空,返回0是非空
STDataType StackTop(Stack* pst);//获取栈顶的数据

2、源文件.c

#include "Stack.h"

void StackInit(Stack* pst)//初始化
{
	assert(pst);
	pst->_a = malloc(sizeof(STDataType)*4);
	pst->_top = 0;
	pst->_capacity = 4;

}
void StackDestory(Stack* pst)//销毁
{
	assert(pst);
	free(pst->_a);
	pst->_a = NULL;
	pst->_top = pst->_capacity = 0;
}
void StackPush(Stack* pst, STDataType x)//入栈
{
	assert(pst);
	if (pst->_top == pst->_capacity)
	{
		pst->_capacity *= 2;
		STDataType* tmp = realloc(pst->_a, sizeof(STDataType) * pst->_capacity);
		if (tmp == NULL)
		{
			printf("内存不足\n");
			exit(-1);
		}
		else
		{
			pst->_a = tmp;
		}
	}
	pst->_a[pst->_top] = x;
	pst->_top++;
}
void StackPop(Stack* pst)//出栈
{
	assert(pst);
	assert(pst->_top > 0);
	--pst->_top;
}
int StackSize(Stack* pst)//获取数据个数
{
	assert(pst);
	return pst->_top;

}
int StackEmpty(Stack* pst)//返回1是空,返回0是非空
{
	assert(pst);
	return pst->_top == 0 ? 1 : 0;
}
STDataType StackTop(Stack* pst)//获取栈顶的数据
{
	assert(pst);
	assert(pst->_top > 0);
	return pst->_a[pst->_top-1];
}

3、测试文件.c

#include "Stack.h"
void TestStack()
{
	Stack st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 4);
	StackPush(&st, 7);
	
	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}
	printf("\n");
}
int main()
{
	TestStack();
	return 0;
}

队列

队列是只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的特性。入队列:进行插入操作的一端称为队尾,出队列:进行删除操作的一端称为队头。队列推荐最好可以用单链表结构实现。一个入队序列只对应一个出队序列。

队列的应用:1、先进先出的场景,例如在保持公平的场景。

利用单链表结构构造一个队列,代码举例:

1、头文件.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* _next;
	QDataType _data;
}QueueNode;

typedef struct Queue
{
	QueueNode* _head;
	QueueNode* _tail;
}Queue;

void QueueInit(Queue* pq);
void QueueDestory(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);

int QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);

2、源文件.c

#include "Queue.h"
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->_head = pq->_tail = NULL;
}
void QueueDestory(Queue* pq)
{
	QueueNode* cur = pq->_head;
	while (cur)
	{
		QueueNode* next = cur->_data;
		free(cur);
		cur = next;
	}
	pq->_head = NULL;
	pq->_tail = NULL;
}
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		printf("内存不足\n");
		exit(-1);
	}
	newnode->_data = x;
	newnode->_next = NULL;
	if (pq->_head == NULL)
	{
		pq->_head = pq->_tail = newnode;
	}
	else
	{
		pq->_tail->_next = newnode;
		pq->_tail = newnode;
	}
}
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->_head);
	QueueNode* next = pq->_head->_next;
	free(pq->_head);
	pq->_head = next;
	if (pq->_head == NULL)
	{
		pq->_tail = NULL;
	}
}
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->_head);
	return pq->_head->_data;
}
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->_tail);
	return pq->_tail->_data;
}

int QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->_head == NULL ? 1 : 0;
}
int QueueSize(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->_head;
	int size = 0;
	while (cur)
	{
		++size;
		cur = cur->_next;
	}
	return size;
}

3、测试文件.c、

#include "Queue.h"


void TestQueue()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");
}
int main()
{
	TestQueue();
	return 0;
}

树是一种非线性的数据结构。它是由n(n>=0)个有限结点组成一个具有层次关系的集合。
有一个特殊的结点,称为根结点,根节点没有前驱结点。除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、…...Tm,其中每一个集合Ti(1<=i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。因此,树是递归定义的。任何一棵树都可以看作是其根节点和其子树构成的。

注:在树形结构中,子树之间是不能有交集的,子树之间不相交。并且除了根结点之外,每个结点有且仅有一个父结点,一棵有N个结点的树一共有N-1条边。

树的相关概念

节点的度:一个节点含有的子树的个数称为该节点的度;如上图:A的为6

叶节点或终端节点:度为0的节点称为叶节点;如上图:B、C、H、....等节点为叶节点

非终端节点或分支节点:度不为0的节点;如上图:D、E、F、G..等节点为分支节点

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;如上图:A是B的父节点

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;如上图:B是A的孩子节点

兄弟节点:具有相同父节点的节点互称为兄弟节点;如上图:B、C是兄弟节点

树的度:一棵树中,最大的节点的度称为树的度;如上图:树的度为6

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;

树的高度或深度:树中节点的最大层次;如上图:树的高度为4

堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点

节点的祖先:从根到该节点所经分支上的所有节点;如上图:是所有节点的祖先

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

森林:由m(m>0)棵互不相交的树的集合称为森林;

树的表示

树的表示常用《左孩子、右兄弟》方法来表示一颗树,具体如下图:

A的左指针指向孩子B,右指针指向空;B的左指针指向孩子D,右指针指向兄弟C;C的左指针指向孩子给,右指针指向空;D的左指针指向空,右指针指向兄弟E;E的左指针指向孩子C,右指针指向兄弟F;F的左指针指向空,右指针指向空;G的左指针指向空,右指针指向空;H的左指针指向空,右指针指向兄弟I;I的左指针指向空,右指针指向空。

二叉树

一棵二叉树可以看成是一个度为2的树,是结点的一个有限集合,该集合可以为空,可以由一个根结点加上两棵左子树和右子树的二叉树组成。二叉树的每个结点最多有两个子树,二叉树不存在度大于2的结点。二叉树的子树有左右之分,其子树的次序不能颠倒。

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。如果一个二又树的层数为K,且结点总数是2^k-1,则它就是满二叉树。

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点--对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。(前K-1层都是满的,最后一层可以不满,但是从左到右必须是连续的)。

二叉树的性质

(1).若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点.
(2).若规定根节点的层数为1,则深度为h的二叉树的最大结点数N是2^h- 1,深度为h的完全二叉树的结点数N是2^h-1-x,x∈[0,2^(h-1)-1]。由此也可以推出,满二叉树的高度h=log2(N+1);完全二叉树的高度h∈[log2(N+1),log2(N)+1]。
(3).对任何一棵二叉树,如果度为0其叶结点个数为 n0,度为2的分支结点个数为 n2,则有no =n2 +1

(4).若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=log2(n+1).
(5).对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:

1.若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点

2.若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子

3.若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

二叉树的存储结构

二叉树一般有两种存储结构,一种是顺序结构,一种是链式结构。

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

对于完全二叉树来说,当已知父亲的下标为i时,可以推出左孩子的下标为2i+1,右孩子的下标为2i+2。当已知孩子的下标为i时,可以推出父亲的下标时(i-1)/2。

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链。

二叉树的遍历

二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。二叉树的遍历有前序、中序、后序、层序遍历。其中前序、中序、后序遍历可以称之为深度优先遍历,层序遍历可以称之为广度优先遍历。

前序遍历表示访问根结点的操作发生在遍历其左右子树之前。

中序遍历表示访问根结点的操作发生在遍历其左右子树中间。

后序遍历表示访问根结点的操作发生在遍历其左右子树之后。

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

typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;

void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->_data);
	PrevOrder(root->_left);
	PrevOrder(root->_right);
}
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->_left);
	printf("%c ", root->_data);
	InOrder(root->_right);
}
void PosOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PosOrder(root->_left);
	PosOrder(root->_right);
	printf("%c ", root->_data);
}
int size = 0;
int TreeSize(BTNode* root)
{
	if (root==NULL)
		return 0;
	++size;
	TreeSize(root->_left);
	TreeSize(root->_right);
	return size;
}

BTNode* CreateNode(int x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	node->_data = x;
	node->_left = NULL;
	node->_right = NULL;
	return node;
}

int main()
{
	BTNode* A = CreateNode('A');
	BTNode* B = CreateNode('B');
	BTNode* C = CreateNode('C');
	BTNode* D = CreateNode('D');
	BTNode* E = CreateNode('E');
	A->_left = B;
	A->_right = C;
	B->_left = D;
	B->_right = E;
	PrevOrder(A);
	printf("\n");
	InOrder(A);
	printf("\n");
	PosOrder(A);
	printf("\n");
	printf("%d ", TreeSize(A));
	getchar();
	return 0;
}

如果有一个关键码的集合K={ko,k, k,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个-维数组中,并满足:K;<= K2*i+1且K¡<= K2*i+2(K¡>= K2*i+1且K;>= K2*i+2)i=0,1,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。堆中某个节点的值总是不大于或不小于其父节点的值;堆总是一棵完全二叉树。

示例:

1、头文件.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h> 
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

void HeapInit(Heap* php,HPDataType* a,int n);
void HeapDestory(Heap* php);
void HeapPush( Heap* php,HPDataType x);
void HeapPop(Heap* php);
HPDataType HeapTop(Heap* php);
void AdjustDown(HPDataType* a, int n, int root);
void HeapPrint(Heap* php);

2、源文件.c

#include "Heap.h"
//交换两个数
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上调整法
void AdjustUp(HPDataType* a, int n, int child)
{
	int parent = (child - 1) / 2;//找到上一层的父结点
	while (child>0)
	{
		if (a[child] < a[parent])//如果子结点小于父结点
		{
			Swap(&a[child], &a[parent]);//交换子节点和父节点
			child = parent;
			parent = (child - 1)/2;//父结点成为子结点,子节点成为新的父节点,实现小的数往上
		}
		else
		{
			break;
		}
	}
}
//向下调整法,前提是左右子树都是小堆,只有根结点不对。原理类似于上面的向上调整
void AdjustDown(HPDataType* a, int n,int root)
{
	int parent = root;
	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 HeapInit(Heap* php, HPDataType* a, int n)
{
	php->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	memcpy(php->_a, a, sizeof(HPDataType) * n);
	php->_size = n;
	php->_capacity = n;

	//构建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(php->_a, php->_size, i);
	}
}
//销毁
void HeapDestory(Heap* php)
{
	assert(php);
	free(php->_a);
	php->_a = NULL;
	php->_capacity = 0;
	php->_size = 0;
}
//加入一个新的数
void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	if (php->_size == php->_capacity)//扩容
	{
		php->_capacity *= 2;
		HPDataType* tmp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * php->_capacity);
		php->_a = tmp;
	}
	php->_a[php->_size++]=x;//将新的数放在最后一个结点上
	AdjustUp(php->_a, php->_size, php->_size - 1);//执行向上调整
}
//删除结点
void HeapPop(Heap* php)
{
	assert(php);
	assert(php->_size > 0);
	Swap(&php->_a[0], &php->_a[php->_size - 1]);
	php->_size--;

	AdjustDown(php->_a, php->_size, 0);//删除上面的某个结点之后,从上往下执行向下调整
}
//获取根节点数据
HPDataType HeapTop(Heap* php)
{
	assert(php);
	return php->_a[0];
}
//打印当前堆
void HeapPrint(Heap* php)
{
	for (int i = 0; i < php->_size; ++i)
	{
		printf("%d ", php->_a[i]);
	}
	printf("\n");
}

3、测试文件.c

# include "Heap.h"

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[] = { 27,15,19,18,28,34,65,49,25,37 };
	//HeapSort(a, sizeof(a)/sizeof(HPDataType));
	//int i = 0;
	//for (i = 0; i < sizeof(a) / sizeof(HPDataType); i++)
	//{
	//	printf("%d ", a[i]);
	//}
	Heap hp;
	HeapInit(&hp,a,sizeof(a)/sizeof(HPDataType));
	printf("Heap elements: ");
	HeapPrint(&hp);

	// 测试堆顶元素
	printf("Top element: %d\n", HeapTop(&hp));
	HeapPush(&hp, 21);
	printf("Heap elements: ");
	HeapPrint(&hp);

	// 测试堆顶元素
	printf("Top element: %d\n", HeapTop(&hp));
	HeapDestory(&hp);
	return 0;
}

排序

排序就是使得一串记录或数据,按照其中的某个或某些关键字的大小,递增或递减的进行排列的操作。常见的排序算法包括:直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序。

直接插入排序

直接插入排序的基本思想就是把待排序的记录或数据按其值得大小逐个插入到一个已经排号得有序序列当中,直到所有的记录或数据插入完毕,得到一个新的有序序列,在实际应用中,常把序列中第一个数当作一个有序序列,然后从后依次进行插入排序。

直接插入排序的特性:(1)元素集合序列越接近有序,直接插入排序算法的时间效率越高。(2)时间复杂度为O(N^2)。(3)空间复杂度为O(1),是一种稳定的排序算法。

希尔排序

希尔排序的基本思想为:先选定一个整数,记为gap;把待排序的数据分为n个组,每个组内的元素为距离相差gap的元素,然后对分好的每一个组组内进行排序,其目的就是让序列接近有序,方便后续直接使用直接插入排序。gap越大,前面大的数可以越快到后面,后面小的数可以越快到前面;但是gap越大越不接近有序,gap越小越接近有序,gap=1时相当于直接插入排序。因为gap的取值方法有很多,所以希尔排序的时间复杂度不好计算,不确定。

以下图为例子:第一趟排序定义gap=5,则相距距离为5的元素分为一组,在图中具体的说就是{9,4},{1,8},{2,6},{5,3},{7,5},然后将各个组内进行排序则变成了{4,9},{1,8},{2,6},{3,5},{5,7},这样完成一趟排序,以此类推当设置不同的gap时,一步步进行排序,直到最后gap=1时,完成全部排序。

直接选择排序

选择排序就是每一次从待排序的数据中选出最小或者最大的一个数据,放在序列的起始或者末尾的位置,直到全部的数据排完。直接选择排序很好理解,但是效率不高;其时间复杂度为O(N^2),其空间复杂度为O(1);排序不稳定。

冒泡排序

冒泡排序是一种交换排序,将较大的数据向序列尾部移动,将较小的数据向序列头部移动。其时间复杂度为O(N^2),空间复杂度为O(1),排序稳定。

快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。其时间复杂度为O(N*logN),空间复杂度为O(logN),排序不稳定。

快速排序如果对已经排序好的序列进行排序的话容易造成栈溢出(如果key的选值还是最左边或者最右边的话)。故提出有一种三数取中法,保证不会取到最小或者最大的值。

归并排序

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序列的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并的缺点在于需要O(N)的空间复杂度,归并排序更多的是解决在磁盘中的外排序问题。其时间复杂度为O(N*logN),空间复杂度为O(N),排序稳定。

计数排序

先统计相同元素出现的次数,然后根据统计的结果将序列覆盖回原序列当中去。该排序方法只适用于整型。

1、头文件

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include<time.h>
// 排序实现的接口
void PrintArray(int* a, int n);
// 插入排序
void InsertSort(int* a, int n);
// 希尔排序
void ShellSort(int* a, int n);
// 选择排序
void SelectSort(int* a, int n);
// 堆排序
void AdjustDwon(int* a, int n, int root);
void HeapSort(int* a, int n);
// 冒泡排序
void BubbleSort(int* a, int n);
// 快速排序递归实现
// 快速排序hoare版本
int PartSort1(int* a, int left, int right);
// 快速排序挖坑法
int PartSort2(int* a, int left, int right);
// 快速排序前后指针法
int PartSort3(int* a, int left, int right);
void QuickSort(int* a, int left, int right);
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right);
// 归并排序递归实现
void MergeSort(int* a, int n);
void _Mergesort(int* a, int left, int right, int* tmp);
// 归并排序非递归实现
void MergeSortNonR(int* a, int n);
// 计数排序
void CountSort(int* a, int n);
// 测试排序的性能对比

2、源文件

#include "Sort.h"
void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
//插入排序
void InsertSort(int* a, int n)
{
	assert(a);
	for (int i = 0; i < n - 1; ++i)
	{
		//把end+1位置的数据插入到[0,end]的有序数列中
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

void ShellSort(int* a, int n)
{
	int gap=n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
		//PrintArray(a, n);
	}
	
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void SelectSort(int* a, int n)
{
	assert(a);
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int mini, maxi;
		mini = maxi = begin;
		for (int i = begin + 1; i <= end; ++i)
		{
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
			if (a[i] < a[mini])
			{
				mini = i;
			}
		}
		Swap(&a[begin], &a[mini]);
		if (begin == maxi)//begin和maxi位置重叠处理
		{
			maxi = mini;
		}
		Swap(&a[end], &a[maxi]);
		++begin;
		--end;
	}
}

void BubbleSort(int* a, int n)
{
	int end = n;
	while (end>0)
	{
		int exchange = 0;
		for (int i = 1; i < end; ++i)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		if (exchange == 0)
		{
			break;
		}
		--end;
	}
	
}

int GetMidIndex(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;
	if (a[begin] < a[mid])
	{
		if (a[mid] < a[end])
		{
			return mid;
		}
		else if (a[begin] > a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
	else
	{
		if (a[mid] > a[end])
		{
			return mid;
		}
		else if (a[begin] < a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
}

int PartSort(int* a, int begin, int end)
{
	//int key = a[end];
	int midIndex = GetMidIndex(a, begin, end);
	Swap(&a[midIndex], &a[end]);
	int keyindex = end;
	while (begin < end)
	{
		//如果选最右边的值做key,一定要让左边的begin先走,这样能保证相遇的位置比key大
		//如果选最左边的值做key,一定要让右边的end先走,这样保证相遇的位置比key小
		//begin找大
		while (begin<end && a[begin] <= a[keyindex])
		{
			++begin;
		}
		//end找小
		while (begin < end && a[end] >= a[keyindex])
		{
			--end;
		}
		//交换
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin],&a[keyindex]);
	return begin;
}

void QuickSort(int* a, int left, int right)
{
	assert(a);
	if (left>right)
	{
		return;
	}
	int div = PartSort(a, left, right);
	QuickSort(a, left, div - 1);
	QuickSort(a, div + 1, right);
}

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
	{
		return;
	}
	//把序列分成两个部分
	int mid = (left + right) / 2;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int index = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	for (int i = left; i <= right; ++i)
	{
		a[i] = tmp[i];
	}
}

void MergeSort(int* a, int n)
{
	assert(a);
	int* tmp = malloc(sizeof(int) * n);
	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}

void CountSort(int* a, int n)
{
	assert(a);
	int min = a[0];
	int max = a[0];
	for (int i = 1; i < n; ++i)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
		if (a[i] < min)
		{
			min = a[i];
		}
	}
	int range = max - min + 1;
	int* countArr = (int*)malloc(sizeof(int) * range);
	memset(countArr, 0, sizeof(int) * range);

	for (int i = 0; i < n; ++i)
	{
		countArr[a[i]-min]++;
	}


	int index = 0;
	for (int j = 0; j < range; ++j)
	{
		while (countArr[j]--)
		{
			a[index++] = j + min;
		}
	}

	free(countArr);
}

3、测试文件

#include"Sort.h"



void TestInsertSort()
{
	int a[] = { 3,4,6,3,2,1,32,55,64,23,4,564,22,44,553,76 };
	PrintArray(a, sizeof(a) / sizeof(int));
	InsertSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestShellSort()
{
	int a[] = { 3,4,6,3,2,1,32,55,64,23,4,564,22,44,553,76 };
	PrintArray(a, sizeof(a) / sizeof(int));
	ShellSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestSelectSort()
{
	int a[] = { 3,4,6,3,2,1,32,55,64,23,4,564,22,44,553,76 };
	PrintArray(a, sizeof(a) / sizeof(int));
	SelectSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestBubbleSort()
{
	int a[] = { 3,4,6,3,2,1,32,55,64,23,4,564,22,44,553,76 };
	PrintArray(a, sizeof(a) / sizeof(int));
	BubbleSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestQuickSort()
{
	int a[] = { 3,4,6,3,2,1,32,55,64,23,4,564,22,44,553,76 };
	PrintArray(a, sizeof(a) / sizeof(int));
	QuickSort(a, 0,sizeof(a) / sizeof(int)-1);
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestMergeSort()
{
	int a[] = { 3,4,6,3,2,1,32,55,64,23,4,564,22,44,553,76 };
	PrintArray(a, sizeof(a) / sizeof(int));
	MergeSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestCountSort()
{
	int a[] = { 3,4,6,3,2,1,32,55,64,23,4,564,22,44,553,76 };
	PrintArray(a, sizeof(a) / sizeof(int));
	CountSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

// 测试排序的性能对比
void TestOP()
{
	srand(time(0));
	const int N = 10000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
	}
	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();
	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();
	/*int begin3 = clock();
	SelectSort(a3, N);
	int end3 = clock();
	int begin4 = clock();
	HeapSort(a4, N);
	int end4 = clock();
	int begin5 = clock();
	QuickSort(a5, 0, N - 1);
	int end5 = clock();
	int begin6 = clock();
	MergeSort(a6, N);
	int end6 = clock();*/
	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	/*printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);*/
	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
}

int main()
{
	/*TestInsertSort();*/
	//TestShellSort();
	//TestOP();
	//TestBubbleSort();
	//TestQuickSort();
	//TestMergeSort();
	TestCountSort();
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值