排序算法:
内部排序和外部排序。
1.内部排序是数据记录在内存中进行排序
2.外部排序因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
常见的内部排序:有如下八种。
插入排序:
1.将第一待排序序列的第一个元素看作一个有序序列,把第二个元素到最后一个元素当成未排序序列。
2.从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插到相等元素的后面)
//插入排序
void insert(int ar[],int len)
{
assert(ar != NULL);
int i;
int j;
int temp;
for (i = 1; i < len; ++i)
{
temp = ar[i];
for (j = i ; j >0 && ar[j-1]>temp; --j)
{
ar[j] = ar[j - 1];
}
ar[j] = temp;
}
}
希尔排序:
递减增量排序算法,将整个待排序的记录序列分割为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对整体全体记录进行依次直接插入排序。
1)选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2)按增量序列个数k,对序列进行k 趟排序;
3)每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
//希尔排序(数组,数组长度)
void shell(int ar[], int len)
{
assert(ar != NULL);
int gap = 0;
for (gap = len / 2; gap > 0; gap /= 2)
{
for (int i = 0; i < len; ++i)
{
int j = 0;
for (j = i + gap; j < len; j += gap)
{
int temp = ar[j];
int k = j - gap;
while (k >= 0 && temp < ar[k])
{
ar[k + gap] = ar[k];
k -= gap;
}
ar[k+gap] = temp;
}
}
}
}
选择排序:
1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
2)再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3)重复第二步,直到所有元素均排序完毕。
//选择排序
void select(int ar[], int len)
{
assert(ar != NULL);
int i;
int j;
int min;
int temp;
for (i = 0; i < len ; ++i)
{
min = i;
for (j = i + 1; j < len; ++j)
{
if (ar[min]>ar[j])
{
min = j;
}
}
temp = ar[i];
ar[i] = ar[min];
ar[min] = temp;
}
}
堆排序:
1)创建一个堆H[0..n-1]
2)把堆首(最大值)和堆尾互换
3)把堆的尺寸缩小1,并调用shift_down(0),目的是把新的数组顶端数据调整到相应位置
4) 重复步骤2,直到堆的尺寸为1
//堆排序
void heap(int ar[], int start, int len)
{
int temp = ar[start];
for (int i = 2 * start + 1; i <= len; i = 2 * i + 1)
{
if (i < len && (ar[i] < ar[i + 1]))
{
i++;
}
if (ar[i]>temp)
{
ar[start] = ar[i];
start = i;
}
else
{
break;
}
}
ar[start] = temp;
}
void myheap(int ar[], int len)
{
assert(ar != NULL);
for (int i = (len - 2) / 2; i >= 0; --i)
{
heap(ar, i, len - 1);
}
for (int i = 0; i < len - 1; ++i)
{
int temp = ar[0];
ar[0] = ar[len - 1 - i];
ar[len - 1 - i] = temp;
heap(ar, 0, len - 1 - 1 - i);
}
}
冒泡排序:
1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
3)针对所有的元素重复以上的步骤,除了最后一个。
4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
//冒泡排序
void bubble(int ar[], int len)
{
assert(ar != NULL);
int i;
int j;
int temp;
for (i = 0; i < len-1; ++i)
{
for (j = 0; j < len-1-i; ++j)
{
if (ar[j]>ar[j+1])
{
temp = ar[j];
ar[j] = ar[j+1];
ar[j+1] = temp;
}
}
}
}
快速排序:
1.从数列中挑出一个元素,称为 “基准”(pivot),
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
//快速排序
int port(int ar[], int start, int end)
{
int temp = ar[start];
while (start<= end)
{
while ((start<end) && (ar[end]>=temp))
{
end--;
}
if (end<=start)
{
break;
}
else
{
ar[start] = ar[end];
}
while ((start < end) && (ar[start] <=temp))
{
start++;
}
if (start >= end)
{
break;
}
else
{
ar[end] = ar[start];
}
}
ar[start] = temp;
return end;
}
void quick(int ar[], int start,int end)
{
assert(ar != NULL);
int temp = port(ar, start, end);
if (temp>start + 1)
{
quick(ar, start, temp - 1);
}
if (temp < end - 1)
{
quick(ar, temp + 1, end);
}
}
基数排序:
将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
typedef struct Node
{
int num;
struct Node* next;
}Node,*List;
//基数排序
void initList(List head)
{
head->next = NULL;
}
Node* getnode(int val)
{
Node *p = (Node *)malloc(sizeof(Node));
assert(p != NULL);
p->num = val;
p->next = NULL;
return p;
}
void inserttail(List head, int val)
{
Node* p = getnode(val);
Node* l = head;
while (l->next != NULL)
{
l = l->next;
}
p->next = l->next;
l->next = p;
}
bool delfirst(List head, int *rtc)
{
Node* p = head->next;
if (rtc != NULL)
{
*rtc = p->num;
}
head->next = p->next;
free(p);
p = NULL;
return true;
}
int getmax(int ar[], int len)
{
int max = 0;
for (int i = 1; i < len; ++i)
{
if (ar[i]>ar[max])
{
max = i;
}
}
cout << ar[max] << endl;
max = ar[max];
int num = 0;
while (max > 0)
{
max /= 10;
num++;
}
return num;
}
int getnum(int ar, int size)
{
for (int i = 0; i < size; ++i)
{
ar /= 10;
}
ar %=10;
return ar;
}
void base(int ar[], int len, int size)
{
Node head[10];
for (int i = 0; i < 10; ++i)
{
initList(&head[i]);
}
for (int i = 0; i < len; ++i)
{
int num = getnum(ar[i], size);
inserttail(&head[num], ar[i]);
}
int k = 0;
int j = 0;
while (j < 10)
{
if (delfirst(&head[j], &ar[k]))
{
k++;
}
else
{
j++;
}
}
}
void mybase(int ar[], int len)
{
assert(ar != NULL);
int num = getmax(ar, len);
cout << "num" << " " <<num<< endl;
for (int i = 0; i < num; ++i)
{
base(ar, len, i);
}
}
归并排序:
1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4. 重复步骤3直到某一指针达到序列尾
5. 将另一序列剩下的所有元素直接复制到合并序列尾
//归并排序
void merge(int ar[], int len,int i)
{
assert(ar != NULL);
int *p = (int *)malloc(sizeof(int)*len);
if (p == NULL)
{
return;
}
int j = 0;
int s1 = 0;
int e1 = s1 + i - 1;
int s2 = e1+1;
int e2 = s2 + i - 1<len-1?s2+i-1:len-1;
while (s2 < len)
{
while ((s1 <= e1) && (s2 <= e2))
{
if (ar[s1] < ar[s2])
{
p[j++] = ar[s1++];
}
else
{
p[j++] = ar[s2++];
}
}
while (s1 <= e1)
{
p[j++] = ar[s1++];
}
while (s2 <= e2)
{
p[j++] = ar[s2++];
}
s1 = e2 + 1;
e1 = s1 + i - 1;
s2 = e1 + 1;
e2 = s2 + i - 1 < len - 1 ? s2 + i - 1 : len - 1;
}
while (s1 < len)
{
p[j++] = ar[s1++];
}
for (int k = 0; k < len; ++k)
{
ar[k] = p[k];
}
free(p);
}
void mymerge(int ar[], int len)
{
for (int i = 1; i < len; i *= 2)
{
merge(ar, len, i);
}
}
数据量小时,可采用插入排序和冒泡排序,因为稳定。
数据基本处于有序时,可采用冒泡和插入或者快排,效率高。
数据量大,可采用快排,堆排序或者归并排序。
数据随机分布,时,快排的效率最高,但堆排空间复杂度低一些,若要求排序稳定可采用归并排序,若排序对内存有要求可选归并,因为读取次数最少,数据量比较大时,把数据放在磁盘上,这时要减少I/O次数-》没有特殊要求用快排。