堆排序
什么是堆?
堆一般都指的是二叉堆,它满足二个特性:
1、父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2、每个结点的左子树和右子树都是一个二叉堆(都是大顶堆或小顶堆)。
如下为一个小顶堆(父结点的键值总是小于任何一个子节点的键值)
由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或二叉树的根)必为序列中n个元素的最小值(或最大值)。 若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。
堆的存储
为了利于存储和运算,一般堆采用完全二叉树表示。而完全二叉树又可以用数组来表示,因此堆可以使用数组来存储。若根结点存在序号0处,
1、i结点的双亲结点下标就为(i-1)/2;
2、i结点的左右子结点下标分别为2*i+1和2*i+2。
(注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)
构造初始堆
构造初始堆:
建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为(n-1)/2的数开始做堆调整,一直到下标为0的数(因为下标大于(n-1)/2的数都是叶子节点,其子树已经满足堆的性质了)。
例如对数组(6,4,2,5,10,7,1,8,3,9)进行初始堆。
堆调整
当除根节点外其他所有结点都满足堆的要求时,对该根结点做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的)(即筛选过程),一直下沉到合适的位置,使得树满足堆的性质。
例如对大顶堆的堆调整我们会这么做:
1、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
2、如果A[i]已经就是最大的元素,则程序直接结束。
3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
相关代码:
#include <stdio.h>
#define MAX 10
void swap(ARR arr,int i,int j)
{
ElementType temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void heap(ARR arr); //堆排序
int main()
{
ARR arr= {6,4,2,5,10,7,1,8,3,9};
heap(arr);
return 0;
}
void heapadjust(ARR arr,int n,int m) //堆调整
{
ElementType temp = arr[n]; //记录当前节点位置
int i;
for(i = 2*n+1;i <= m;i = 2*i+1)
{
if(i < m && arr[i] < arr[i+1]) //比较左子树右子树
i++; //如果右孩子元素大于左孩子则堆顶元素的值为右孩子
if(arr[i] < temp) //比较完后的孩子元素与节点比较
break;
arr[n] = arr[i]; //如果大于则该值赋值给节点
n = i;
}
arr[n] = temp; //当前节点的值用temp替换
}
void heap(ARR arr) //堆排序
{
int i;
for(i = (MAX-2)/2;i >= 0;i--) //初始化堆
heapadjust(arr,i,MAX-1); //进行堆调整
for(i = MAX - 1;i > 0;i--)
{
swap(arr,0,i); //调整完后堆顶与最后一位元素(第i个)调换,完成后断开连接
heapadjust(arr,0,i-1); //再进行堆顶元素与第i-1个元素的堆调整
print(arr);
}
}
归并排序
算法简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
一、归并排序具体算法描述如下(递归版本):
1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
2、Conquer: 对这两个子序列分别采用归并排序。
3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。
二、非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
#include <stdio.h>
#define MAX 10
typedef int ElementType;
typedef ElementType ARR[MAX];
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void merge(ARR arr,int left,int mid,int right); //进行归并
void mergesort1(ARR arr); //非递归的归并排序
void mergesort2(ARR arr,int left,int right); //递归的归并排序
int main()
{
ARR arr = {6,4,2,5,10,7,1,8,3,9};
mergesort1(arr);
mergesort2(arr,0,MAX-1);
return 0;
}
void merge(ARR arr,int left,int mid,int right) //进行归并
{
int len = right - left + 1;
ElementType temp[len];
int k = 0;
int i = left; // 前一数组的起始元素
int j = mid + 1; // 后一数组的起始元素
while(i <= mid && j <= right)
{
if(arr[i] < arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while(i <= mid)
temp[k++] = arr[i++];
while(j <= right)
temp[k++] = arr[j++];
for(i = 0;i < len;i++)
arr[left + i] = temp[i];
}
//归并排序
void mergesort1(ARR arr) //非递归的归并排序
{
int left,right,mid;
int i;
for(i = 1;i < MAX;i *= 2) //i为步长每轮翻倍
{
left = 0;
while(left + i < MAX) // 后一个子数组存在
{
mid = left + i - 1;
right = (mid+i) < (MAX-1) ? (mid+i) : (MAX-1); // 后一个子数组大小可能不够
merge(arr,left,mid,right);
left = right + 1; 前一个子数组向后移动
}
print(arr);
}
void mergesort2(ARR arr,int left,int right) //递归的归并排序
{
if(left == right)
return;
int mid = (left + right) / 2;
mergesort2(arr,left,mid);
mergesort2(arr,mid+1,right);
merge(arr,left,mid,right);
print(arr);
}
堆排序
什么是堆?
堆一般都指的是二叉堆,它满足二个特性:
1、父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2、每个结点的左子树和右子树都是一个二叉堆(都是大顶堆或小顶堆)。
如下为一个小顶堆(父结点的键值总是小于任何一个子节点的键值)
由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或二叉树的根)必为序列中n个元素的最小值(或最大值)。 若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。
堆的存储
为了利于存储和运算,一般堆采用完全二叉树表示。而完全二叉树又可以用数组来表示,因此堆可以使用数组来存储。若根结点存在序号0处,
1、i结点的双亲结点下标就为(i-1)/2;
2、i结点的左右子结点下标分别为2*i+1和2*i+2。
(注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)
构造初始堆
构造初始堆:
建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为(n-1)/2的数开始做堆调整,一直到下标为0的数(因为下标大于(n-1)/2的数都是叶子节点,其子树已经满足堆的性质了)。
例如对数组(6,4,2,5,10,7,1,8,3,9)进行初始堆。
堆调整
当除根节点外其他所有结点都满足堆的要求时,对该根结点做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的)(即筛选过程),一直下沉到合适的位置,使得树满足堆的性质。
例如对大顶堆的堆调整我们会这么做:
1、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
2、如果A[i]已经就是最大的元素,则程序直接结束。
3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
相关代码:
#include <stdio.h>
#define MAX 10
void swap(ARR arr,int i,int j)
{
ElementType temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void heap(ARR arr); //堆排序
int main()
{
ARR arr= {6,4,2,5,10,7,1,8,3,9};
heap(arr);
return 0;
}
void heapadjust(ARR arr,int n,int m) //堆调整
{
ElementType temp = arr[n]; //记录当前节点位置
int i;
for(i = 2*n+1;i <= m;i = 2*i+1)
{
if(i < m && arr[i] < arr[i+1]) //比较左子树右子树
i++; //如果右孩子元素大于左孩子则堆顶元素的值为右孩子
if(arr[i] < temp) //比较完后的孩子元素与节点比较
break;
arr[n] = arr[i]; //如果大于则该值赋值给节点
n = i;
}
arr[n] = temp; //当前节点的值用temp替换
}
void heap(ARR arr) //堆排序
{
int i;
for(i = (MAX-2)/2;i >= 0;i--) //初始化堆
heapadjust(arr,i,MAX-1); //进行堆调整
for(i = MAX - 1;i > 0;i--)
{
swap(arr,0,i); //调整完后堆顶与最后一位元素(第i个)调换,完成后断开连接
heapadjust(arr,0,i-1); //再进行堆顶元素与第i-1个元素的堆调整
print(arr);
}
}
归并排序
算法简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
一、归并排序具体算法描述如下(递归版本):
1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
2、Conquer: 对这两个子序列分别采用归并排序。
3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。
二、非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
#include <stdio.h>
#define MAX 10
typedef int ElementType;
typedef ElementType ARR[MAX];
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void merge(ARR arr,int left,int mid,int right); //进行归并
void mergesort1(ARR arr); //非递归的归并排序
void mergesort2(ARR arr,int left,int right); //递归的归并排序
int main()
{
ARR arr = {6,4,2,5,10,7,1,8,3,9};
mergesort1(arr);
mergesort2(arr,0,MAX-1);
return 0;
}
void merge(ARR arr,int left,int mid,int right) //进行归并
{
int len = right - left + 1;
ElementType temp[len];
int k = 0;
int i = left; // 前一数组的起始元素
int j = mid + 1; // 后一数组的起始元素
while(i <= mid && j <= right)
{
if(arr[i] < arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while(i <= mid)
temp[k++] = arr[i++];
while(j <= right)
temp[k++] = arr[j++];
for(i = 0;i < len;i++)
arr[left + i] = temp[i];
}
//归并排序
void mergesort1(ARR arr) //非递归的归并排序
{
int left,right,mid;
int i;
for(i = 1;i < MAX;i *= 2) //i为步长每轮翻倍
{
left = 0;
while(left + i < MAX) // 后一个子数组存在
{
mid = left + i - 1;
right = (mid+i) < (MAX-1) ? (mid+i) : (MAX-1); // 后一个子数组大小可能不够
merge(arr,left,mid,right);
left = right + 1; 前一个子数组向后移动
}
print(arr);
}
void mergesort2(ARR arr,int left,int right) //递归的归并排序
{
if(left == right)
return;
int mid = (left + right) / 2;
mergesort2(arr,left,mid);
mergesort2(arr,mid+1,right);
merge(arr,left,mid,right);
print(arr);
}
堆排序
什么是堆?
堆一般都指的是二叉堆,它满足二个特性:
1、父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2、每个结点的左子树和右子树都是一个二叉堆(都是大顶堆或小顶堆)。
如下为一个小顶堆(父结点的键值总是小于任何一个子节点的键值)
由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或二叉树的根)必为序列中n个元素的最小值(或最大值)。 若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。
堆的存储
为了利于存储和运算,一般堆采用完全二叉树表示。而完全二叉树又可以用数组来表示,因此堆可以使用数组来存储。若根结点存在序号0处,
1、i结点的双亲结点下标就为(i-1)/2;
2、i结点的左右子结点下标分别为2*i+1和2*i+2。
(注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)
构造初始堆
构造初始堆:
建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为(n-1)/2的数开始做堆调整,一直到下标为0的数(因为下标大于(n-1)/2的数都是叶子节点,其子树已经满足堆的性质了)。
例如对数组(6,4,2,5,10,7,1,8,3,9)进行初始堆。
堆调整
当除根节点外其他所有结点都满足堆的要求时,对该根结点做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的)(即筛选过程),一直下沉到合适的位置,使得树满足堆的性质。
例如对大顶堆的堆调整我们会这么做:
1、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
2、如果A[i]已经就是最大的元素,则程序直接结束。
3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
相关代码:
#include <stdio.h>
#define MAX 10
void swap(ARR arr,int i,int j)
{
ElementType temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void heap(ARR arr); //堆排序
int main()
{
ARR arr= {6,4,2,5,10,7,1,8,3,9};
heap(arr);
return 0;
}
void heapadjust(ARR arr,int n,int m) //堆调整
{
ElementType temp = arr[n]; //记录当前节点位置
int i;
for(i = 2*n+1;i <= m;i = 2*i+1)
{
if(i < m && arr[i] < arr[i+1]) //比较左子树右子树
i++; //如果右孩子元素大于左孩子则堆顶元素的值为右孩子
if(arr[i] < temp) //比较完后的孩子元素与节点比较
break;
arr[n] = arr[i]; //如果大于则该值赋值给节点
n = i;
}
arr[n] = temp; //当前节点的值用temp替换
}
void heap(ARR arr) //堆排序
{
int i;
for(i = (MAX-2)/2;i >= 0;i--) //初始化堆
heapadjust(arr,i,MAX-1); //进行堆调整
for(i = MAX - 1;i > 0;i--)
{
swap(arr,0,i); //调整完后堆顶与最后一位元素(第i个)调换,完成后断开连接
heapadjust(arr,0,i-1); //再进行堆顶元素与第i-1个元素的堆调整
print(arr);
}
}
归并排序
算法简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
一、归并排序具体算法描述如下(递归版本):
1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
2、Conquer: 对这两个子序列分别采用归并排序。
3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。
二、非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
#include <stdio.h>
#define MAX 10
typedef int ElementType;
typedef ElementType ARR[MAX];
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void merge(ARR arr,int left,int mid,int right); //进行归并
void mergesort1(ARR arr); //非递归的归并排序
void mergesort2(ARR arr,int left,int right); //递归的归并排序
int main()
{
ARR arr = {6,4,2,5,10,7,1,8,3,9};
mergesort1(arr);
mergesort2(arr,0,MAX-1);
return 0;
}
void merge(ARR arr,int left,int mid,int right) //进行归并
{
int len = right - left + 1;
ElementType temp[len];
int k = 0;
int i = left; // 前一数组的起始元素
int j = mid + 1; // 后一数组的起始元素
while(i <= mid && j <= right)
{
if(arr[i] < arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while(i <= mid)
temp[k++] = arr[i++];
while(j <= right)
temp[k++] = arr[j++];
for(i = 0;i < len;i++)
arr[left + i] = temp[i];
}
//归并排序
void mergesort1(ARR arr) //非递归的归并排序
{
int left,right,mid;
int i;
for(i = 1;i < MAX;i *= 2) //i为步长每轮翻倍
{
left = 0;
while(left + i < MAX) // 后一个子数组存在
{
mid = left + i - 1;
right = (mid+i) < (MAX-1) ? (mid+i) : (MAX-1); // 后一个子数组大小可能不够
merge(arr,left,mid,right);
left = right + 1; 前一个子数组向后移动
}
print(arr);
}
void mergesort2(ARR arr,int left,int right) //递归的归并排序
{
if(left == right)
return;
int mid = (left + right) / 2;
mergesort2(arr,left,mid);
mergesort2(arr,mid+1,right);
merge(arr,left,mid,right);
print(arr);
}
堆排序
什么是堆?
堆一般都指的是二叉堆,它满足二个特性:
1、父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2、每个结点的左子树和右子树都是一个二叉堆(都是大顶堆或小顶堆)。
如下为一个小顶堆(父结点的键值总是小于任何一个子节点的键值)
由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或二叉树的根)必为序列中n个元素的最小值(或最大值)。 若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。
堆的存储
为了利于存储和运算,一般堆采用完全二叉树表示。而完全二叉树又可以用数组来表示,因此堆可以使用数组来存储。若根结点存在序号0处,
1、i结点的双亲结点下标就为(i-1)/2;
2、i结点的左右子结点下标分别为2*i+1和2*i+2。
(注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)
构造初始堆
构造初始堆:
建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为(n-1)/2的数开始做堆调整,一直到下标为0的数(因为下标大于(n-1)/2的数都是叶子节点,其子树已经满足堆的性质了)。
例如对数组(6,4,2,5,10,7,1,8,3,9)进行初始堆。
堆调整
当除根节点外其他所有结点都满足堆的要求时,对该根结点做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的)(即筛选过程),一直下沉到合适的位置,使得树满足堆的性质。
例如对大顶堆的堆调整我们会这么做:
1、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
2、如果A[i]已经就是最大的元素,则程序直接结束。
3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
相关代码:
#include <stdio.h>
#define MAX 10
void swap(ARR arr,int i,int j)
{
ElementType temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void heap(ARR arr); //堆排序
int main()
{
ARR arr= {6,4,2,5,10,7,1,8,3,9};
heap(arr);
return 0;
}
void heapadjust(ARR arr,int n,int m) //堆调整
{
ElementType temp = arr[n]; //记录当前节点位置
int i;
for(i = 2*n+1;i <= m;i = 2*i+1)
{
if(i < m && arr[i] < arr[i+1]) //比较左子树右子树
i++; //如果右孩子元素大于左孩子则堆顶元素的值为右孩子
if(arr[i] < temp) //比较完后的孩子元素与节点比较
break;
arr[n] = arr[i]; //如果大于则该值赋值给节点
n = i;
}
arr[n] = temp; //当前节点的值用temp替换
}
void heap(ARR arr) //堆排序
{
int i;
for(i = (MAX-2)/2;i >= 0;i--) //初始化堆
heapadjust(arr,i,MAX-1); //进行堆调整
for(i = MAX - 1;i > 0;i--)
{
swap(arr,0,i); //调整完后堆顶与最后一位元素(第i个)调换,完成后断开连接
heapadjust(arr,0,i-1); //再进行堆顶元素与第i-1个元素的堆调整
print(arr);
}
}
归并排序
算法简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
一、归并排序具体算法描述如下(递归版本):
1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
2、Conquer: 对这两个子序列分别采用归并排序。
3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。
二、非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
#include <stdio.h>
#define MAX 10
typedef int ElementType;
typedef ElementType ARR[MAX];
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void merge(ARR arr,int left,int mid,int right); //进行归并
void mergesort1(ARR arr); //非递归的归并排序
void mergesort2(ARR arr,int left,int right); //递归的归并排序
int main()
{
ARR arr = {6,4,2,5,10,7,1,8,3,9};
mergesort1(arr);
mergesort2(arr,0,MAX-1);
return 0;
}
void merge(ARR arr,int left,int mid,int right) //进行归并
{
int len = right - left + 1;
ElementType temp[len];
int k = 0;
int i = left; // 前一数组的起始元素
int j = mid + 1; // 后一数组的起始元素
while(i <= mid && j <= right)
{
if(arr[i] < arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while(i <= mid)
temp[k++] = arr[i++];
while(j <= right)
temp[k++] = arr[j++];
for(i = 0;i < len;i++)
arr[left + i] = temp[i];
}
//归并排序
void mergesort1(ARR arr) //非递归的归并排序
{
int left,right,mid;
int i;
for(i = 1;i < MAX;i *= 2) //i为步长每轮翻倍
{
left = 0;
while(left + i < MAX) // 后一个子数组存在
{
mid = left + i - 1;
right = (mid+i) < (MAX-1) ? (mid+i) : (MAX-1); // 后一个子数组大小可能不够
merge(arr,left,mid,right);
left = right + 1; 前一个子数组向后移动
}
print(arr);
}
void mergesort2(ARR arr,int left,int right) //递归的归并排序
{
if(left == right)
return;
int mid = (left + right) / 2;
mergesort2(arr,left,mid);
mergesort2(arr,mid+1,right);
merge(arr,left,mid,right);
print(arr);
}
堆排序
什么是堆?
堆一般都指的是二叉堆,它满足二个特性:
1、父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2、每个结点的左子树和右子树都是一个二叉堆(都是大顶堆或小顶堆)。
如下为一个小顶堆(父结点的键值总是小于任何一个子节点的键值)
由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或二叉树的根)必为序列中n个元素的最小值(或最大值)。 若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。
堆的存储
为了利于存储和运算,一般堆采用完全二叉树表示。而完全二叉树又可以用数组来表示,因此堆可以使用数组来存储。若根结点存在序号0处,
1、i结点的双亲结点下标就为(i-1)/2;
2、i结点的左右子结点下标分别为2*i+1和2*i+2。
(注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)
构造初始堆
构造初始堆:
建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为(n-1)/2的数开始做堆调整,一直到下标为0的数(因为下标大于(n-1)/2的数都是叶子节点,其子树已经满足堆的性质了)。
例如对数组(6,4,2,5,10,7,1,8,3,9)进行初始堆。
堆调整
当除根节点外其他所有结点都满足堆的要求时,对该根结点做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的)(即筛选过程),一直下沉到合适的位置,使得树满足堆的性质。
例如对大顶堆的堆调整我们会这么做:
1、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
2、如果A[i]已经就是最大的元素,则程序直接结束。
3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
相关代码:
#include <stdio.h>
#define MAX 10
void swap(ARR arr,int i,int j)
{
ElementType temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void heap(ARR arr); //堆排序
int main()
{
ARR arr= {6,4,2,5,10,7,1,8,3,9};
heap(arr);
return 0;
}
void heapadjust(ARR arr,int n,int m) //堆调整
{
ElementType temp = arr[n]; //记录当前节点位置
int i;
for(i = 2*n+1;i <= m;i = 2*i+1)
{
if(i < m && arr[i] < arr[i+1]) //比较左子树右子树
i++; //如果右孩子元素大于左孩子则堆顶元素的值为右孩子
if(arr[i] < temp) //比较完后的孩子元素与节点比较
break;
arr[n] = arr[i]; //如果大于则该值赋值给节点
n = i;
}
arr[n] = temp; //当前节点的值用temp替换
}
void heap(ARR arr) //堆排序
{
int i;
for(i = (MAX-2)/2;i >= 0;i--) //初始化堆
heapadjust(arr,i,MAX-1); //进行堆调整
for(i = MAX - 1;i > 0;i--)
{
swap(arr,0,i); //调整完后堆顶与最后一位元素(第i个)调换,完成后断开连接
heapadjust(arr,0,i-1); //再进行堆顶元素与第i-1个元素的堆调整
print(arr);
}
}
归并排序
算法简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
一、归并排序具体算法描述如下(递归版本):
1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
2、Conquer: 对这两个子序列分别采用归并排序。
3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。
二、非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
#include <stdio.h>
#define MAX 10
typedef int ElementType;
typedef ElementType ARR[MAX];
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void merge(ARR arr,int left,int mid,int right); //进行归并
void mergesort1(ARR arr); //非递归的归并排序
void mergesort2(ARR arr,int left,int right); //递归的归并排序
int main()
{
ARR arr = {6,4,2,5,10,7,1,8,3,9};
mergesort1(arr);
mergesort2(arr,0,MAX-1);
return 0;
}
void merge(ARR arr,int left,int mid,int right) //进行归并
{
int len = right - left + 1;
ElementType temp[len];
int k = 0;
int i = left; // 前一数组的起始元素
int j = mid + 1; // 后一数组的起始元素
while(i <= mid && j <= right)
{
if(arr[i] < arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while(i <= mid)
temp[k++] = arr[i++];
while(j <= right)
temp[k++] = arr[j++];
for(i = 0;i < len;i++)
arr[left + i] = temp[i];
}
//归并排序
void mergesort1(ARR arr) //非递归的归并排序
{
int left,right,mid;
int i;
for(i = 1;i < MAX;i *= 2) //i为步长每轮翻倍
{
left = 0;
while(left + i < MAX) // 后一个子数组存在
{
mid = left + i - 1;
right = (mid+i) < (MAX-1) ? (mid+i) : (MAX-1); // 后一个子数组大小可能不够
merge(arr,left,mid,right);
left = right + 1; 前一个子数组向后移动
}
print(arr);
}
void mergesort2(ARR arr,int left,int right) //递归的归并排序
{
if(left == right)
return;
int mid = (left + right) / 2;
mergesort2(arr,left,mid);
mergesort2(arr,mid+1,right);
merge(arr,left,mid,right);
print(arr);
}
堆排序
什么是堆?
堆一般都指的是二叉堆,它满足二个特性:
1、父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2、每个结点的左子树和右子树都是一个二叉堆(都是大顶堆或小顶堆)。
如下为一个小顶堆(父结点的键值总是小于任何一个子节点的键值)
由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或二叉树的根)必为序列中n个元素的最小值(或最大值)。 若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。
堆的存储
为了利于存储和运算,一般堆采用完全二叉树表示。而完全二叉树又可以用数组来表示,因此堆可以使用数组来存储。若根结点存在序号0处,
1、i结点的双亲结点下标就为(i-1)/2;
2、i结点的左右子结点下标分别为2*i+1和2*i+2。
(注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)
构造初始堆
构造初始堆:
建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为(n-1)/2的数开始做堆调整,一直到下标为0的数(因为下标大于(n-1)/2的数都是叶子节点,其子树已经满足堆的性质了)。
例如对数组(6,4,2,5,10,7,1,8,3,9)进行初始堆。
堆调整
当除根节点外其他所有结点都满足堆的要求时,对该根结点做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的)(即筛选过程),一直下沉到合适的位置,使得树满足堆的性质。
例如对大顶堆的堆调整我们会这么做:
1、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
2、如果A[i]已经就是最大的元素,则程序直接结束。
3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
相关代码:
#include <stdio.h>
#define MAX 10
void swap(ARR arr,int i,int j)
{
ElementType temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void heap(ARR arr); //堆排序
int main()
{
ARR arr= {6,4,2,5,10,7,1,8,3,9};
heap(arr);
return 0;
}
void heapadjust(ARR arr,int n,int m) //堆调整
{
ElementType temp = arr[n]; //记录当前节点位置
int i;
for(i = 2*n+1;i <= m;i = 2*i+1)
{
if(i < m && arr[i] < arr[i+1]) //比较左子树右子树
i++; //如果右孩子元素大于左孩子则堆顶元素的值为右孩子
if(arr[i] < temp) //比较完后的孩子元素与节点比较
break;
arr[n] = arr[i]; //如果大于则该值赋值给节点
n = i;
}
arr[n] = temp; //当前节点的值用temp替换
}
void heap(ARR arr) //堆排序
{
int i;
for(i = (MAX-2)/2;i >= 0;i--) //初始化堆
heapadjust(arr,i,MAX-1); //进行堆调整
for(i = MAX - 1;i > 0;i--)
{
swap(arr,0,i); //调整完后堆顶与最后一位元素(第i个)调换,完成后断开连接
heapadjust(arr,0,i-1); //再进行堆顶元素与第i-1个元素的堆调整
print(arr);
}
}
归并排序
算法简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
一、归并排序具体算法描述如下(递归版本):
1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
2、Conquer: 对这两个子序列分别采用归并排序。
3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。
二、非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
#include <stdio.h>
#define MAX 10
typedef int ElementType;
typedef ElementType ARR[MAX];
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void merge(ARR arr,int left,int mid,int right); //进行归并
void mergesort1(ARR arr); //非递归的归并排序
void mergesort2(ARR arr,int left,int right); //递归的归并排序
int main()
{
ARR arr = {6,4,2,5,10,7,1,8,3,9};
mergesort1(arr);
mergesort2(arr,0,MAX-1);
return 0;
}
void merge(ARR arr,int left,int mid,int right) //进行归并
{
int len = right - left + 1;
ElementType temp[len];
int k = 0;
int i = left; // 前一数组的起始元素
int j = mid + 1; // 后一数组的起始元素
while(i <= mid && j <= right)
{
if(arr[i] < arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while(i <= mid)
temp[k++] = arr[i++];
while(j <= right)
temp[k++] = arr[j++];
for(i = 0;i < len;i++)
arr[left + i] = temp[i];
}
//归并排序
void mergesort1(ARR arr) //非递归的归并排序
{
int left,right,mid;
int i;
for(i = 1;i < MAX;i *= 2) //i为步长每轮翻倍
{
left = 0;
while(left + i < MAX) // 后一个子数组存在
{
mid = left + i - 1;
right = (mid+i) < (MAX-1) ? (mid+i) : (MAX-1); // 后一个子数组大小可能不够
merge(arr,left,mid,right);
left = right + 1; 前一个子数组向后移动
}
print(arr);
}
void mergesort2(ARR arr,int left,int right) //递归的归并排序
{
if(left == right)
return;
int mid = (left + right) / 2;
mergesort2(arr,left,mid);
mergesort2(arr,mid+1,right);
merge(arr,left,mid,right);
print(arr);
}
堆排序
什么是堆?
堆一般都指的是二叉堆,它满足二个特性:
1、父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2、每个结点的左子树和右子树都是一个二叉堆(都是大顶堆或小顶堆)。
如下为一个小顶堆(父结点的键值总是小于任何一个子节点的键值)
由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或二叉树的根)必为序列中n个元素的最小值(或最大值)。 若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。
堆的存储
为了利于存储和运算,一般堆采用完全二叉树表示。而完全二叉树又可以用数组来表示,因此堆可以使用数组来存储。若根结点存在序号0处,
1、i结点的双亲结点下标就为(i-1)/2;
2、i结点的左右子结点下标分别为2*i+1和2*i+2。
(注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)
构造初始堆
构造初始堆:
建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为(n-1)/2的数开始做堆调整,一直到下标为0的数(因为下标大于(n-1)/2的数都是叶子节点,其子树已经满足堆的性质了)。
例如对数组(6,4,2,5,10,7,1,8,3,9)进行初始堆。
堆调整
当除根节点外其他所有结点都满足堆的要求时,对该根结点做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的)(即筛选过程),一直下沉到合适的位置,使得树满足堆的性质。
例如对大顶堆的堆调整我们会这么做:
1、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
2、如果A[i]已经就是最大的元素,则程序直接结束。
3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
相关代码:
#include <stdio.h>
#define MAX 10
void swap(ARR arr,int i,int j)
{
ElementType temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void heap(ARR arr); //堆排序
int main()
{
ARR arr= {6,4,2,5,10,7,1,8,3,9};
heap(arr);
return 0;
}
void heapadjust(ARR arr,int n,int m) //堆调整
{
ElementType temp = arr[n]; //记录当前节点位置
int i;
for(i = 2*n+1;i <= m;i = 2*i+1)
{
if(i < m && arr[i] < arr[i+1]) //比较左子树右子树
i++; //如果右孩子元素大于左孩子则堆顶元素的值为右孩子
if(arr[i] < temp) //比较完后的孩子元素与节点比较
break;
arr[n] = arr[i]; //如果大于则该值赋值给节点
n = i;
}
arr[n] = temp; //当前节点的值用temp替换
}
void heap(ARR arr) //堆排序
{
int i;
for(i = (MAX-2)/2;i >= 0;i--) //初始化堆
heapadjust(arr,i,MAX-1); //进行堆调整
for(i = MAX - 1;i > 0;i--)
{
swap(arr,0,i); //调整完后堆顶与最后一位元素(第i个)调换,完成后断开连接
heapadjust(arr,0,i-1); //再进行堆顶元素与第i-1个元素的堆调整
print(arr);
}
}
归并排序
算法简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
一、归并排序具体算法描述如下(递归版本):
1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
2、Conquer: 对这两个子序列分别采用归并排序。
3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。
二、非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
#include <stdio.h>
#define MAX 10
typedef int ElementType;
typedef ElementType ARR[MAX];
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void merge(ARR arr,int left,int mid,int right); //进行归并
void mergesort1(ARR arr); //非递归的归并排序
void mergesort2(ARR arr,int left,int right); //递归的归并排序
int main()
{
ARR arr = {6,4,2,5,10,7,1,8,3,9};
mergesort1(arr);
mergesort2(arr,0,MAX-1);
return 0;
}
void merge(ARR arr,int left,int mid,int right) //进行归并
{
int len = right - left + 1;
ElementType temp[len];
int k = 0;
int i = left; // 前一数组的起始元素
int j = mid + 1; // 后一数组的起始元素
while(i <= mid && j <= right)
{
if(arr[i] < arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while(i <= mid)
temp[k++] = arr[i++];
while(j <= right)
temp[k++] = arr[j++];
for(i = 0;i < len;i++)
arr[left + i] = temp[i];
}
//归并排序
void mergesort1(ARR arr) //非递归的归并排序
{
int left,right,mid;
int i;
for(i = 1;i < MAX;i *= 2) //i为步长每轮翻倍
{
left = 0;
while(left + i < MAX) // 后一个子数组存在
{
mid = left + i - 1;
right = (mid+i) < (MAX-1) ? (mid+i) : (MAX-1); // 后一个子数组大小可能不够
merge(arr,left,mid,right);
left = right + 1; 前一个子数组向后移动
}
print(arr);
}
void mergesort2(ARR arr,int left,int right) //递归的归并排序
{
if(left == right)
return;
int mid = (left + right) / 2;
mergesort2(arr,left,mid);
mergesort2(arr,mid+1,right);
merge(arr,left,mid,right);
print(arr);
}
堆排序
什么是堆?
堆一般都指的是二叉堆,它满足二个特性:
1、父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2、每个结点的左子树和右子树都是一个二叉堆(都是大顶堆或小顶堆)。
如下为一个小顶堆(父结点的键值总是小于任何一个子节点的键值)
由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或二叉树的根)必为序列中n个元素的最小值(或最大值)。 若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。
堆的存储
为了利于存储和运算,一般堆采用完全二叉树表示。而完全二叉树又可以用数组来表示,因此堆可以使用数组来存储。若根结点存在序号0处,
1、i结点的双亲结点下标就为(i-1)/2;
2、i结点的左右子结点下标分别为2*i+1和2*i+2。
(注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)
构造初始堆
构造初始堆:
建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为(n-1)/2的数开始做堆调整,一直到下标为0的数(因为下标大于(n-1)/2的数都是叶子节点,其子树已经满足堆的性质了)。
例如对数组(6,4,2,5,10,7,1,8,3,9)进行初始堆。
堆调整
当除根节点外其他所有结点都满足堆的要求时,对该根结点做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的)(即筛选过程),一直下沉到合适的位置,使得树满足堆的性质。
例如对大顶堆的堆调整我们会这么做:
1、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
2、如果A[i]已经就是最大的元素,则程序直接结束。
3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
相关代码:
#include <stdio.h>
#define MAX 10
void swap(ARR arr,int i,int j)
{
ElementType temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void heap(ARR arr); //堆排序
int main()
{
ARR arr= {6,4,2,5,10,7,1,8,3,9};
heap(arr);
return 0;
}
void heapadjust(ARR arr,int n,int m) //堆调整
{
ElementType temp = arr[n]; //记录当前节点位置
int i;
for(i = 2*n+1;i <= m;i = 2*i+1)
{
if(i < m && arr[i] < arr[i+1]) //比较左子树右子树
i++; //如果右孩子元素大于左孩子则堆顶元素的值为右孩子
if(arr[i] < temp) //比较完后的孩子元素与节点比较
break;
arr[n] = arr[i]; //如果大于则该值赋值给节点
n = i;
}
arr[n] = temp; //当前节点的值用temp替换
}
void heap(ARR arr) //堆排序
{
int i;
for(i = (MAX-2)/2;i >= 0;i--) //初始化堆
heapadjust(arr,i,MAX-1); //进行堆调整
for(i = MAX - 1;i > 0;i--)
{
swap(arr,0,i); //调整完后堆顶与最后一位元素(第i个)调换,完成后断开连接
heapadjust(arr,0,i-1); //再进行堆顶元素与第i-1个元素的堆调整
print(arr);
}
}
归并排序
算法简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
一、归并排序具体算法描述如下(递归版本):
1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
2、Conquer: 对这两个子序列分别采用归并排序。
3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。
二、非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
#include <stdio.h>
#define MAX 10
typedef int ElementType;
typedef ElementType ARR[MAX];
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void merge(ARR arr,int left,int mid,int right); //进行归并
void mergesort1(ARR arr); //非递归的归并排序
void mergesort2(ARR arr,int left,int right); //递归的归并排序
int main()
{
ARR arr = {6,4,2,5,10,7,1,8,3,9};
mergesort1(arr);
mergesort2(arr,0,MAX-1);
return 0;
}
void merge(ARR arr,int left,int mid,int right) //进行归并
{
int len = right - left + 1;
ElementType temp[len];
int k = 0;
int i = left; // 前一数组的起始元素
int j = mid + 1; // 后一数组的起始元素
while(i <= mid && j <= right)
{
if(arr[i] < arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while(i <= mid)
temp[k++] = arr[i++];
while(j <= right)
temp[k++] = arr[j++];
for(i = 0;i < len;i++)
arr[left + i] = temp[i];
}
//归并排序
void mergesort1(ARR arr) //非递归的归并排序
{
int left,right,mid;
int i;
for(i = 1;i < MAX;i *= 2) //i为步长每轮翻倍
{
left = 0;
while(left + i < MAX) // 后一个子数组存在
{
mid = left + i - 1;
right = (mid+i) < (MAX-1) ? (mid+i) : (MAX-1); // 后一个子数组大小可能不够
merge(arr,left,mid,right);
left = right + 1; 前一个子数组向后移动
}
print(arr);
}
void mergesort2(ARR arr,int left,int right) //递归的归并排序
{
if(left == right)
return;
int mid = (left + right) / 2;
mergesort2(arr,left,mid);
mergesort2(arr,mid+1,right);
merge(arr,
堆排序
什么是堆?
堆一般都指的是二叉堆,它满足二个特性:
1、父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2、每个结点的左子树和右子树都是一个二叉堆(都是大顶堆或小顶堆)。
如下为一个小顶堆(父结点的键值总是小于任何一个子节点的键值)
由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或二叉树的根)必为序列中n个元素的最小值(或最大值)。 若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。
堆的存储
为了利于存储和运算,一般堆采用完全二叉树表示。而完全二叉树又可以用数组来表示,因此堆可以使用数组来存储。若根结点存在序号0处,
1、i结点的双亲结点下标就为(i-1)/2;
2、i结点的左右子结点下标分别为2*i+1和2*i+2。
(注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)
构造初始堆
构造初始堆:
建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为(n-1)/2的数开始做堆调整,一直到下标为0的数(因为下标大于(n-1)/2的数都是叶子节点,其子树已经满足堆的性质了)。
例如对数组(6,4,2,5,10,7,1,8,3,9)进行初始堆。
堆调整
当除根节点外其他所有结点都满足堆的要求时,对该根结点做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的)(即筛选过程),一直下沉到合适的位置,使得树满足堆的性质。
例如对大顶堆的堆调整我们会这么做:
1、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
2、如果A[i]已经就是最大的元素,则程序直接结束。
3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
相关代码:
#include <stdio.h>
#define MAX 10
void swap(ARR arr,int i,int j)
{
ElementType temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void heap(ARR arr); //堆排序
int main()
{
ARR arr= {6,4,2,5,10,7,1,8,3,9};
heap(arr);
return 0;
}
void heapadjust(ARR arr,int n,int m) //堆调整
{
ElementType temp = arr[n]; //记录当前节点位置
int i;
for(i = 2*n+1;i <= m;i = 2*i+1)
{
if(i < m && arr[i] < arr[i+1]) //比较左子树右子树
i++; //如果右孩子元素大于左孩子则堆顶元素的值为右孩子
if(arr[i] < temp) //比较完后的孩子元素与节点比较
break;
arr[n] = arr[i]; //如果大于则该值赋值给节点
n = i;
}
arr[n] = temp; //当前节点的值用temp替换
}
void heap(ARR arr) //堆排序
{
int i;
for(i = (MAX-2)/2;i >= 0;i--) //初始化堆
heapadjust(arr,i,MAX-1); //进行堆调整
for(i = MAX - 1;i > 0;i--)
{
swap(arr,0,i); //调整完后堆顶与最后一位元素(第i个)调换,完成后断开连接
heapadjust(arr,0,i-1); //再进行堆顶元素与第i-1个元素的堆调整
print(arr);
}
}
归并排序
算法简介
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
一、归并排序具体算法描述如下(递归版本):
1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
2、Conquer: 对这两个子序列分别采用归并排序。
3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。
二、非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
#include <stdio.h>
#define MAX 10
typedef int ElementType;
typedef ElementType ARR[MAX];
void print(ARR arr)
{
int i;
for(i = 0;i < MAX;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void merge(ARR arr,int left,int mid,int right); //进行归并
void mergesort1(ARR arr); //非递归的归并排序
void mergesort2(ARR arr,int left,int right); //递归的归并排序
int main()
{
ARR arr = {6,4,2,5,10,7,1,8,3,9};
mergesort1(arr);
mergesort2(arr,0,MAX-1);
return 0;
}
void merge(ARR arr,int left,int mid,int right) //进行归并
{
int len = right - left + 1;
ElementType temp[len];
int k = 0;
int i = left; // 前一数组的起始元素
int j = mid + 1; // 后一数组的起始元素
while(i <= mid && j <= right)
{
if(arr[i] < arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
while(i <= mid)
temp[k++] = arr[i++];
while(j <= right)
temp[k++] = arr[j++];
for(i = 0;i < len;i++)
arr[left + i] = temp[i];
}
//归并排序
void mergesort1(ARR arr) //非递归的归并排序
{
int left,right,mid;
int i;
for(i = 1;i < MAX;i *= 2) //i为步长每轮翻倍
{
left = 0;
while(left + i < MAX) // 后一个子数组存在
{
mid = left + i - 1;
right = (mid+i) < (MAX-1) ? (mid+i) : (MAX-1); // 后一个子数组大小可能不够
merge(arr,left,mid,right);
left = right + 1; 前一个子数组向后移动
}
print(arr);
}
void mergesort2(ARR arr,int left,int right) //递归的归并排序
{
if(left == right)
return;
int mid = (left + right) / 2;
mergesort2(arr,left,mid);
mergesort2(arr,mid+1,right);
merge(arr,left,mid,right);
print(arr);
}
left,mid,right);
print(arr);
}