什么是排序?
排序(sort)就是要整理表中的元素,使之按关键字递增或递减有序排列。
排序的稳定性:
经过排序后这些具有相同关键字的元素之间的相对次序保持不变,则称这种排序方法是稳定的,反之,具有相同关键字的元素的相对次序发生变化,则称这种排序是不稳定的。
内排序和外排序:
在排序过程中,若整个表都放在内存中处理,排序时不涉及数据的内,外交换,称为内排序。
反之,排序过程中涉及数据的内,外交换,称之为外排序。
插入排序的基本思想:
每次将一个待排序的元素按其关键字大小插入到前面已经排好序的子表中的适当位置,直到全部元素插入完成为止。
3种插入排序算法:
直接插入排序,折半插入排序,希尔排序。
直接插入排序:
待排序的数组R[],将其分为两个子数组,一个是有序区,一个是无序区,初始时有序区有0个元素。将无序区开头的元素取出,暂存到temp,从后向前找有序区中关键字小于或等于temp.key的,将temp放在它的后面。时间复杂度为O(n^2)
function insertSort(arr) {
var len =arr.length;
for (var i=1;i<len; i++) {
var temp=arr[i];
var j=i-1;//默认已排序的元素
while (j>=0 && arr[j]>temp) {
//在已排序好的队列中从后向前扫描
arr[j+1]=arr[j];
//已排序的元素大于新元素,该元素将移到下一个位置
j--;
}
arr[j+1]=temp;
}
return arr
}
二分查找法:
1.从第一个元素开始,认为该元素已排序。
2,取出下一个元素,在已排序序列中二分查找到第一个比它大的数的位置
3,将元素插入到该位置后
4,重复上述两步
时间复杂度为O(n^2)
function binaryInsertSort(arr) {
var len =arr.length;
for (var i=1;i<len; i++) {
var key=arr[i],left=0,right=i-1;
while(left<=right){
//在已排序的元素中二分查找第一个比它大的值
var mid= parseInt((left+right)/2);
//二分查找的中间值
if(key<arr[mid]){
//当前值比中间值小 则在左边的子数组中继续寻找 right = mid-1;
}else{
left=mid+1;
//当前值比中间值大 在右边的子数组继续寻找
}
}
for(var j=i-1;j>=left;j--){
arr[j+1]=arr[j];
}
arr[left]=key;
}
return arr;
}
插入排序的不适合大数据量的排序,一般来说适合小数据量排序,如n<1000,插入排序也作为快排的补充,当n<8时,使用插排,否则使用快排。
希尔排序实际上是一种分组插入方法,又称为缩小增量排序,基本思想是设待排序元素序列有n个元素,首先取一个整数increment(小于n)作为间隔将全部元素分为increment个子序列,所有距离为increment的元素放在同一个子序列中,在每一个子序列中分别实行直接插入排序。然后缩小间隔increment,重复上述子序列划分和排序工作。直到最后取increment=1,将所有元素放在同一个子序列中排序为止。
(2)由于开始时,increment的取值较大,每个子序列中的元素较少,排序速度较快,到排序后期increment取值逐渐变小,子序列中元素个数逐渐增多,但由于前面工作的基础,大多数元素已经基本有序,所以排序速度仍然很快。
增量increment的取法有各种方案。最初shell提出取increment=n/2向下取整,increment=increment/2向下取整,直到increment=1。但由于直到最后一步,在奇数位置的元素才会与偶数位置的元素进行比较,这样使用这个序列的效率会很低。后来Knuth提出取increment=n/3向下取整+1.还有人提出都取奇数为好,也有人提出increment互质为好。应用不同的序列会使希尔排序算法的性能有很大的差异。
function shellSort(arr) {
for(let gap = Math.floor(arr.length/2); gap > 0; gap = Math.floor(gap/2)) {
// 内层循环与插入排序的写法基本一致,只是每次移动的步长变为 gap
for(let i = gap; i < arr.length; i++) {
let j = i;
let temp = arr[j];
for(; j> 0; j -= gap) {
if(temp >= arr[j-gap]) {
break;
}
arr[j] = arr[j-gap];
}
arr[j] = temp;
}
}
return arr;
}
交换排序:(冒泡排序和快速排序)
的思想是两两比较待排序元素的关键字,发现这两个元素的次序相反时即进行交换,直到没有反序元素为止
function sortarr(arr){
for(i=0;i<arr.length-1;i++){
for(j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
var temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
return arr;
}
快速排序(Quicksort)是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
function quickSort(arr) {
if(arr.length<=1){
return arr;
}
var mid = arr.shift();
var left = [];
var right = [];
for(var i = 0;i<arr.length;i++){
if(arr[i]<mid){
left.push(arr[i]);
}
else{
right.push(arr[i]);
}
}
return quickSort(left).concat(mid,quickSort(right));
}
选择排序的思想是:把每一个数都与第一个数比较,如果小于第一个数,就把它们交换位置;这样一轮下来,最小的数就排到了最前面;重复n-1轮,就实现了选择排序
简单选择排序:
function selectSort(arr){
var len=arr.length;
var temp;
for(var i=0;i<len-1;i++){
for(var j=i+1;j<len;j++){
if(arr[j]<arr[i]){
temp=arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
}
}
return arr;
}
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大根堆arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] ;
或者每个结点的值都小于或等于其左右孩子结点的值,称为小根堆arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2] 。
var len;
function buildMaxHeap(arr) { //建堆
len = arr.length;
// [n/2-1]表示的是最后一个有子节点 (本来是n/2(堆从1数起),
但是这里arr索引是从0开始,所以-1)
for (var i = Math.floor(len/2)-1; i>=0; i--) {
maxHeapify(arr, i); } //对每一个节点(非叶节点),做堆调整}function maxHeapify(arr, i) {
//堆调整
var left = 2*i+1,
right = 2*i+2,
largest = i; //i为该子树的根节点
if (left < len && arr[left] > arr[largest]) {
largest = left; }
if (right < len && arr[right] > arr[largest]) {
largest = right; }
if (largest != i) { //即上面的if中有一个生效了
swap(arr, i, largest); //交换最大的为父节点
maxHeapify(arr, largest);
//交换后,原值arr[i](往下降了)(
索引保存为largest),
//作为根时,子节点可能比它大,因此要继续调整 } }
function swap(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;}
function heapSort(arr) {
buildMaxHeap(arr);
for (var i = arr.length-1; i > 0; i--) {
swap(arr, 0, i);
len--;
maxHeapify(arr, 0); }
return arr;}
归并排序
function merge(left, right) {
var result = [];
while(left.length > 0 && right.length > 0) {
if(left[0] < right[0]) {
result.push(left.shift());
}
else {
result.push(right.shift());
}
} /* 当左右数组长度不等.将比较完后剩下的数组项链接起来即可 */ return result.concat(left).concat(right);
}
function mergeSort(arr){
if(arr.length==1) {
return arr
};
var mid=Math.floor(arr.length/2);
var left_arr=arr.slice(0,mid),right_arr=arr.slice(mid);
return merge(mergeSort(left_arr),mergeSort(right_arr)); }
基数排序
function RadixLSDSort (arr, digit) {
const radix = 10; // 基数,以10进制来进行排序
var i = 0,
j = 0,
count = Array(radix), // 0~9的桶
end = arr.length,
bucket = Array(end);
// 利用LSD,也就是次位优先
for (var d = 1; d <= digit; d++) {
for (i = 0; i < radix; i++) {
count[i] = 0;
}
// 向各个桶中添加元素,并统计出每个桶中装的个数
for (i = 0; i < end; i++) {
j = getDigit(arr[i], d);
count[j]++;
}
// count的越往后值最大,最大值为arr.length
// count数组的值为,该位数值为该索引的数字总数
for (i = 1; i < radix; i++) {
count[i] = count[i] + count[i - 1];
}
// 按照桶的顺序将导入temp中
for (i = end - 1; i >= 0; i--) {
j = getDigit(arr[i], d);
bucket[count[j] - 1] = arr[i];
count[j]--;
}
// 将已经根据相应位数排好的序列导回arr中
for (i = 0; i < end; i++) {
arr[i] = bucket[i];
}
}
}