提到排序算法,大家首先想到的肯定是冒泡和快排了,因为冒泡排序最简单,快速排序速度最快。今天就来介绍一下这两个排序算法。
冒泡排序
首先来看冒泡排序,其基本思想是一组数据,两两两个对其进行比较,将大的数据元素置于右边,小的数据元素置于左边,这样最大的元素将会置于最右边,接着重复进行之前的操作,以此类推,直至最左边的元素都有序,此时所有数据元素都变得有序。如下图:
代码如下:
void BubbleSort(int *arr, int len)
{
int i;
int j=0;
int tmp;
for (i = 0; i < len-1; i++)
{
for (j = 0; j < len-i-1 ; j++)
{
if (arr[j] >arr[j + 1])
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
可以说冒泡排序是所有排序算法中最简单的,同时也是大家最熟知的排序算法。
链式冒泡排序
当数据是使用链式结构存储时,使用冒泡排序也是可以的,代码如下:
typedef struct Node
{
int data;
struct Node *next;
}Node,*PList;
static Node *BuyNode(int val)
{
Node *p = (Node*)malloc(sizeof(Node));
assert(NULL != p);
p->data = val;
p->next = NULL;
return p;
}
void InitList(PList plist)
{
assert(NULL != plist);
plist->next = NULL;
}
void Insert_tail(PList plist, int val)
{
Node *p = BuyNode(val);
Node *q;
for (q = plist; q->next != NULL; q = q->next)
{
;
}
q->next = p;
}
int GetLength(PList plist)
{
int count = 0;
for (Node *p = plist->next; p != NULL; p = p->next)
{
count++;
}
return count;
}
void Show_list(PList plist)
{
for (Node *p = plist->next; p != NULL; p = p->next)
{
printf("%d ", p->data);
}
printf("\n");
}
//链式冒泡排序
void BubbleSort_list(PList plist)
{
int tmp;
int len = GetLength(plist);
for (int i = 0; i < len - 1; i++)
{
for (Node*p = plist->next; p->next != NULL; p = p->next)
{
if (p->data > p->next->data)
{
tmp = p->data;
p->data = p->next->data;
p->next->data = tmp;
}
}
}
}
快速排序
下面我们来看一下快速排序。快速排序的基本思想是,一组数据将第一个数据和最后一个数据看作是两个哨兵,同时将第一个数据作为基准,从后往前遍历,比基准大的数据往后移动,比基准小的数据往前移动,当两个哨兵相遇时,操作结束。这样的操作过程称为快速排序的一趟划分。接着对基准前面和后面的数据分别进行划分,直至所有的数据都变得完全有序。
当快速排序的一次划分结束后,哨兵相遇的地方就是基准数据的位置,而且自此往后基准的位置将不会在发生移动。值得注意的是,虽然一次划分后,数据并没有变得完全有序,但基准的位置已经确定了,因为基准前面的数据都比基准小,后面的数据都比基准大,所以当前位置就是基准的最终位置。如下图所示:
代码如下:
快速排序的递归实现
//划分函数
static int Partition(int *arr, int left, int right)
{
int tmp=arr[left]; //基准
while (left<right)
{
while ((left<right) && (arr[right]>=tmp))
{
right--;
}
if (right == left)
{
break;
}
else
{
arr[left] = arr[right];
}
while ((left < right) && arr[left] <= tmp)
{
left++;
}
if (left == right)
{
break;
}
else
{
arr[right] = arr[left];
}
}
arr[left] = tmp;
return left;
}
static void Quick(int *arr, int left, int right)
{
int par = Partition(arr, left, right);
if (left + 1 < par) //划分基准左边
{
Quick(arr,left,par-1);
}
if (par < right - 1) //划分基准右边
{
Quick(arr,par+1,right);
}
}
//快速排序
void QuickSort(int *arr, int len)
{
Quick(arr, 0, len - 1);
}
快速排序的非递归实现
首先思考一下,如果不使用递归,会出现什么问题。我想大家都应该想到了,不使用递归,每一次划分的起始位置和结束位置无法保存,并且随着划分的进行,这样的数据会越来越多,使用递归的好处是我们不需要主动去保存这些数据,而是想让系统去做这些事情,但是这样是需要付出代价的,那就是递归是很消耗系统内存的,那么我们在不使用递归的情况下该怎么去解决这个问题呢?
解决办法就是,我们可以使用数据结构去存储这些数据,能想到的就是使用栈和队列,但是这里我们推荐使用栈。
原因是快速排序在划分结束后将数据分成了三部分,基准,基准前和基准后,如果基准前或者基准后的数据在划分的时候快要划分有序了。相比于使用队列,使用栈会使程序的效率更高一些,根据栈先进后出的特点,程序会将待划分的数据段划分到最终有序然后释放掉划分完的那部分数据。如果使用队列的话,在划分数据段的时候,根据队列先进先出的特点,程序会划分另外一段数据段,这样系统维护数据内存的周期会更长一些,这样会导致程序的效率下降。
如图所示:
代码如下:
//划分函数
static int Partition(int *arr, int left, int right)
{
int tmp=arr[left]; //基准
while (left<right)
{
while ((left<right) && (arr[right]>=tmp))
{
right--;
}
if (right == left)
{
break;
}
else
{
arr[left] = arr[right];
}
while ((left < right) && arr[left] <= tmp)
{
left++;
}
if (left == right)
{
break;
}
else
{
arr[right] = arr[left];
}
}
arr[left] = tmp;
return left;
}
/快速排序非递归实现
void QuickSort(int *arr, int len)
{
//log10函数需要math.h头文件
int size = (int)malloc(log10((double)len) / log10((double)2))+1;
int *stack = (int *)malloc(size * 2 * sizeof(int));
assert(NULL != stack);
int top = 0; //栈顶指针,当前可以存放数据的下标
int left = 0;
int right = len - 1;
int par = Partition(arr, left, right);
if (left + 1 < par)
{
stack[top++] = left;
stack[top++] = par - 1;
}
if (par < right - 1)
{
stack[top++] = par + 1;
stack[top++] = right;
}
while (top > 0)
{
right = stack[--top];
left = stack[--top];
par = Partition(arr, left, right);
if (left + 1 < par)
{
stack[top++] = left;
stack[top++] = par - 1;
}
if (par < right - 1)
{
stack[top++] = par + 1;
stack[top++] = right;
}
}
free(stack);
}