将第i个记录插入已经排好序的i-1个元素中,每次依次比较和移动,i从2-n(第一个视为已经排好序的元素),保留一个key存放array[i]的数据,以防数据大的元素后移使会覆盖掉要插入的数据。
每次只要找到比要插入的数据大的数据就交换两个元素的位置
void insert(int *array, int size)
{
for (int i = 1; i < size; i++)
{
int key = array[i];//作为保留的array[i],防止在移动中覆盖原有数据
int j = i - 1;
while (array[j]>key)
{
array[j + 1] = array[j];
j--;
}
if ((j+1)!=i)//如果顺序已经顺序,就不用在排了
array[j+1] = key;
}
}
int main()
{
int array[] = { 48, 62, 35, 77, 55, 14, 35, 98 };
int size = sizeof(array) / sizeof(array[0]);
insert(array, size);
print(array, size);
system("pause");
}
算法核心
时间复杂度 该算法的耗时只在比较和移动元素上
在依据上述直接插入的例子中,比较和插入是同时进行的,那么,是否可以对其相应拆解,各自做优化呢??我们知道对于有序数组的查找二分查找是相对比较优化的选择方案,其时间复杂度大致为log2n,对于顺序表元素的插入只能依次向后逐渐插入,谈不上优化
所以折半查找的算法思想就是先用二分查找找到相应的插入位置,再进行元素的依次移动
void insert_op(int *array, int size)
{
for (int i = 1; i < size; i++)
{
int left = 0;
int right = i - 1;//前i-1个已排好序的数据中查找位置
int key = array[i];//带插入元素
//查找待插入的位置
while (left <= right)
{
int mid = left + ((right - left) >> 1);
if (array[mid]>key)
{
right = mid - 1;
}
else
{
left = mid + 1;
}
}
//此时left即为带插入的位置
//移动元素位置
if (i != left)
{
for (int j = i - 1; j >= left; --j)
{
array[j + 1] = array[j];
}
array[left] = key;
}
}
}
int main()
{
int array[] = { 48, 62, 35, 77, 55, 14, 35, 98 };
int size = sizeof(array) / sizeof(array[0]);
//insert(array, size);
insert_op(array, size);
print(array, size);
system("pause");
}
直接插入排序算法,在待插入的元素基本有序且n较小时,其算法的性能最佳。
- 为什么用希尔排序
而希尔算法依据直接插入的思想,将数据按增量划分多个子序列,对子序列进行直接插入排序。使得当N较大时,也能按照较优的性能进行插入排序。(n较小)
对子序列进行直接插入排序后,使得总的序列基本有序,也能按照较优的性能进行插入排序。(基本有序)
上述的增量是按照d=d/2的算法实现的,但是可以看到是存在缺陷的,除最后一次d=1外,其他的情况下偶数位置和奇数位置是无法进行比较的。
所以改进后的d=d/3+1;当然,增量的取值算法是不唯一的,这是较为优化的一种。
void shell(int *array, int size)
{
int gap = size;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = gap; i < size; i++)
{
int left = i - gap;
int key = array[i];
//设置循环的主要目的是为了能够按照增量从后往前一次的对数据进行排序,保证了每条增量线上的元素是有序的
while (array[left]>key && left >= 0)
{
array[left+gap] = array[left];
left = left - gap;
}
array[left + gap] = key;
}
}
}
int main()
{
int array[] = { 48, 62, 35, 77, 55, 14, 35, 98 };
int size = sizeof(array) / sizeof(array[0]);
//insert(array, size);
//insert_op(array, size);
shell(array, size);
print(array, size);
system("pause");
}
非常简单的一种排序算法,进行n-1趟的排序,以第i趟为例,若是逆序排列的话,需要进行n-i次的比较,进行3(n-i)次交换。所以经过N-1次的交换排序后,总的比较次数为n-1+……+2+1=n(n-1)/2次的比较,需要进行3n(n-1)/2的交换。
每排序依次将最大的元素移至数组的最大下标出,下一次只需比较n-2的位置即可,进行n-1次的排序。
void swap(int *left, int *right)
{
int tmp = *left;
*left = *right;
*right = tmp;
}
void BubbleSort(int *array, int size)
{
int change = 0;
for (int i = 0; i < size - 1; i++)
{
change = 0;
for (int j = 0; j < size - 1 - i; j++)
{
if (array[j]>array[j + 1])
{
swap(&array[j], &array[j + 1]);
change = 1;
}
}
if (!change)
return;
}
}
int main()
{
int array[] = { 48, 62, 35, 77, 55, 14, 35, 98 };
int size = sizeof(array) / sizeof(array[0]);
BubbleSort(array, size);
print(array, size);
return 0;
}
依据冒泡排序的思想,改善了冒泡排序只可以交换相邻两个元素的缺点,可以交换不相邻的两个元素,从而大大提高了排序的速度。
找一个基准值,将大于基准值的排在基准值的后面,将小于基准值的排在基准值的前面。这样就分割成了两个字表,再将连个字表按照上述规则再进行划分,直至字表的长度不超过1。
int GetMidDataIndex(int* array, int left, int right)
{
int mid = left + ((right - left) >> 1);
if (array[left] < array[right-1])
{
if (array[mid]>array[right-1])
return right-1;
else if (array[mid] < array[left])
return left;
else
return mid;
}
else
{
if (array[mid]<array[right-1])
return right-1;
else if (array[mid] > array[left])
return left;
else
return mid;
}
}
而对于相应的一趟的快速排序,在这里详细讲解三种方法。
int QKPass1(int *array, int left, int right)
{
int pos = GetMidDataIndex(array, left, right);
int begin = left;
int end = right-1;
int key = array[pos];
swap(&array[pos], &array[begin]);
while (begin < end)
{
while (array[end]>key && begin < end)
{
end--;
}
//array[pos] = array[end];
//在这里出现了问题,由于第一次是end和pos的位置交换,导致后面再进行begin和end交换时,无法实现
//作出改进,将pos处的值与begin处的值在最开始未进入循环的时候先进行交换
if (begin < end)
{
array[begin] = array[end];
begin++;//此处右边悬空,要让左侧走
}
while (array[begin] < key&&begin < end)
{
begin++;
}
if (begin < end)
{
array[end] = array[begin];
end--;//此处左边悬空,要让右侧走
}
}
array[begin] = key;
return begin;
}
利用两个指针,主要让后面的指针寻找比基准值小的值,若是此时前面的指针指向的值比基准值大,则交换两个元素的值,否则各自往后一找寻合适的交换位置。
最终的prev的位置即为基准值。
int QKPass2(int *array, int left, int right)
{
int pcur = left;
int prev = pcur - 1;
int pos = GetMidDataIndex(array, left, right);
int key = array[pos];
swap(&array[pos], &array[right - 1]);
while (pcur < right)
{
if (array[pcur] < key && (++prev) != pcur)
{
if (array[prev]>key)
swap(&array[prev], &array[pcur]);
else
prev++;
}
pcur++;
}
swap(&array[++prev], &array[right - 1]);
//特别注意此处,要将prev++
return prev;
}
和前后指针法类似,一个从最左边开始找大于key的,一个从最右边开始找小于key的,找到两者交换。直到最后begin==end
int QKPass3(int *array, int left, int right)
{
int begin = 0;
int end = right - 2;//不需要和最后一个key值做比较
int pos = GetMidDataIndex(array, left, right);
int key = array[pos];
swap(&array[pos], &array[right - 1]);
while (begin < end)
{
while (array[begin] < key&&begin < end)
{
begin++;
}
while (array[end]> key&&begin < end)
{
end--;
}
if (begin < end)
swap(&array[begin], &array[end]);
begin++;
end--;
}
swap(&array[begin], &array[right - 1]);
return begin;
}
void QK(int *array, int left, int right)
{
if (left < right)
{
int pos = QKPass3(array, left, right);
QK(array, left, pos);
QK(array, pos + 1, right);
}
}
快排就是一分为二的思想,所以利用栈的思想也可以完美的解决。先让右数入队列,再让左数入队列,在依次取栈顶元素,依次出栈。这样就可以实现递归了。
每次在无序队列中“选择”出最小值,放到有序队列的最后,并从无序队列中去除该值
选择排序的平均时间复杂度比冒泡排序的稍低
原因:
同样数据的情况下,两种算法的循环次数是一样的,但选择排序只有0到1次交换,而冒泡排序只有0到n次交换。
冒泡排序是每一次都可能要交换,而选择排序是比较时记下a[i]的位置,最后来交换,所以其交换的过程是不一样的,但查找的过程是一样的。
void selectsort(int *array, int size)
{
for (int i = 0; i < size-1; ++i)//n-1趟
{
int maxpos = 0;
for (int j = 1; j < size - i; ++j)
{
if (array[maxpos] < array[j])
{
maxpos = j;
}
}
if (maxpos != size - i-1)
{
swap(&array[maxpos],&array[size - i-1]);
}
}
}
没有最好和最坏的情况,对于不管数组开始处于何种状态,必须进行1+2+….n=n(n-1)/2=O(n^2)次的比较
所以时间复杂度为O(n^2)
空间复杂度为O(1)
有什么不足吗??
每次比较的时候只选择最大的,可以对上述算法加以改进,每次选出最大的和最小的,这样比较的次数可以减小
void selectsort_op(int *array, int size)
{
int begin = 0;
int end = size - 1;
while (begin < end)
{
int minpos = begin;
int maxpos = begin;
for (int j =begin + 1; j <= end ; j++)
{
if (array[minpos]>array[j])
{
minpos = j;
}
if (array[maxpos] < array[j])
{
maxpos = j;
}
}
if (minpos != begin)
{
swap(&array[minpos], &array[begin]);
}
if (maxpos == begin)
{
maxpos = minpos;
}
if (maxpos != end)
{
swap(&array[maxpos], &array[end]);
}
begin++;
end--;
}
}
不分最好情况和最坏情况这样比较的次数就变成了n-1+n-3+…..+1=n(n-1)/4,当n较大时,效果比普通的选择排序的效果要好。
堆并排序的思想就是讲一个大的数组分成若干小的数组,在将这些小的数组依次排序,对这些以排好的小数组在进行合并。
在这里讲一下二路归并
当然依据二路归并还可以设计出多路归并
void Mergetdata(int *array[], int left,int mid, int right, int *tmp)
{
int begin1 = left;
int end1 = mid;
int begin2 = mid + 1;
int end2 = right;
int index = left;
while (begin1 <= end1&&begin2 <= end2)
{
if (array[begin1] < array[begin2])
{
tmp[index++] = array[begin1++];
}
else
{
tmp[index++] = array[begin2++];
}
}
while (begin1 <= end1)
{
tmp[index++] = array[begin1++];
}
while (begin2 <= end2 )
{
tmp[index++] = array[begin2++];
}
}
void _MergeSort(int *array[], int left, int right, int *tmp)
{
if (left < right)
{
int mid = left + ((right - left) >> 1);
_MergeSort(array, left, mid, tmp);
_MergeSort(array, mid + 1, right, tmp);
Mergetdata(array, left, mid, right, tmp);
memcpy(array+left, tmp+left, sizeof(array[0])*(right - left + 1));
}
}
void MergeSort(int *array[], int size)
{
int *tmp = (int *)malloc(sizeof(array[0])*size);
if (tmp == NULL)
return;
_MergeSort(array, 0, size-1, tmp);
free(tmp);
}
int main()
{
int array[] = { 19,13,5,27,1,26,31,16};
MergeSort(array, sizeof(array) / sizeof(array[0]));
print(array, sizeof(array) / sizeof(array[0]) );
system("pause");
return 0;
}
这里采用递归的思想,先从左递归直到排序的长度为2,即先排左array[0]-array[1],然后再排右array[2]-array[3],然后再左array[0]-array[3],在右array[4-7]的左array[4]-array[5],在右array[6]-array[7],在右array[4]-array[7],在总array[0]-array[7];
void MergeSortNor(int *array[], int size)
{
int gap = 1;
int *tmp = (int *)malloc(sizeof(array[0])*size + 1);
if (tmp == NULL)
return;
while (gap < size)
{
for (int i = 0; i < size; i += 2 * gap)
{
int left = i;
int right = i + gap+(gap-1);
int mid = left + ((right - left) >> 1);
Mergetdata(array, left, mid, right, tmp);
}
memcpy(array, tmp, sizeof(array[0])*size);
gap *= 2;
}
}
先两个两个一排,即array[0-1],arry[2-3],array[4-5],array[6-7],再四个四个一排,array[0-3],array[4-7],再八个一排array[0-7]
1.鸽巢原理
即将所有数据重新以计数的方式排列一遍,然后依次放在原来的数组中,进行覆盖,存储的数据已经不是原来的数了。
void CountSort(int* array, int size)
{
int mindata;
int maxdata;
mindata = array[0];
maxdata = array[0];
int i = 0;
//确定数据范围 O(N)
for (i = 1; i < size; i++)
{
if (array[i] < mindata)
{
mindata = array[i];
}
if (array[i] > maxdata)
{
maxdata = array[i];
}
}
int range = maxdata - mindata + 1;
int *pcount = (int *)malloc(sizeof(int)*range);
if (pcount == NULL)
{
return;
}
memset(pcount, 0, sizeof(int)*range);
i = 0;
//统计每个元素出现的次数O(N)
for (i = 0; i < size; i++)
{
pcount[array[i] - mindata]++;
}
//回收数据
i = 0;
int index = 0;
while (i < range)
{
while (pcount[i]--)
{
array[index++] = i + mindata;
}
i++;
}
free(pcount);
}
1.先获取数组中最大数的位数
2.获取count数组中取余相等的个数
3.通过count计算哈希表中每个元素的起始位置
3.按起始位置将元素依次放入哈希表中
4.依次从低位开始按位排序,第一次先将个位数排好,第二次将十位数排好,第三次将百位数排好…….
int GetBitNum(int *array[], int size)
{
int i = 0;
int count = 1;
int index = 10;
for (i; iwhile (array[i]>index)
{
count++;
index *= 10;
}
}
return count;
}
void RadixSortLSD(int* array, int size)
{
int i, bitdex, bitnum;
bitnum = GetBitNum(array, size);
int index = 1;
int *bucket = (int *)malloc(sizeof(int)*size);
if (bucket == NULL)
{
return;
}
for (bitdex = 0; bitdex < bitnum; bitdex++)
{
int count[10] = { 0 };
int startaddr[10] = { 0 };
//统计每个桶中元素的个数
for (i = 0; i < size; i++)
{
count[array[i] / index % 10]++;
}
//计算起始位置
for (i = 1; i < size; i++)
{
startaddr[i] = startaddr[i - 1] + count[i - 1];
}
//放入桶中
for (i = 0; i < size; i++)
{
bucket[startaddr[array[i]/index%10]++] = array[i];
}
memcpy(array, bucket, sizeof(int)*size);
index *= 10;
}
free(bucket);
}
int main()
{
//int array[] = { 5, 8, 9, 12, 10, 8, 7, 10, 6 };
int array[] = { 5, 9, 8, 15, 69, 53, 102, 134, 187, 126 };
int size = sizeof(array) / sizeof(array[0]);
RadixSortLSD(array, size);
print(array, size);
return 0;
}
先找出比最高位低的第二位,(5, 9, 8, 15, 69, 53,)将其先按照从小到大一次排列,在将102, 134, 187, 126 按照从小到大依次排列
void _RadixSortMSD(int* array, int left, int right, int* bucket, int bit)
{
int i;
int count[10] = { 0 };
int startAddr[10] = { left };
int radix = (int)pow((double)10, bit);
if (bit < 0)
return;
// 统计每个桶中有效元素的个数
for (i = left; i < right; ++i)
count[array[i] / radix % 10]++;
// 计算每个桶的起始地址
for (i = 1; i < 10; ++i)
startAddr[i] = startAddr[i - 1] + count[i - 1];
// 将元素按照当前位放置到对应的桶中
for (i = left; i < right; ++i)
{
int bucketNo = array[i] / radix % 10;
bucket[startAddr[bucketNo]++] = array[i];
}
// 对元素进行回收
memcpy(array + left, bucket + left, (right - left)*sizeof(array[left]));
for (i = 0; i < 10; ++i)
{
int begin = startAddr[i] - count[i];
int end = startAddr[i];
if (begin + 1 >= end)
continue;
_RadixSortMSD(array, begin, end, bucket, bit - 1);
}
}
void RadixSortMSD(int* array, int size)
{
int* bucket = (int*)malloc(sizeof(array[0])*size);
if (NULL == bucket)
return;
_RadixSortMSD(array, 0, size, bucket, GetBitNum(array, size) - 1);
free(bucket);
}
int main()
{
//int array[] = { 5, 8, 9, 12, 10, 8, 7, 10, 6 };
int array[] = { 5, 9, 8, 15, 69, 53, 102, 134, 187, 126 };
int size = sizeof(array) / sizeof(array[0]);
RadixSortMSD(array, size);
print(array, size);
return 0;
}