DS-内部排序

 目录

1、插入排序

1.1直接插入排序

(1)基于顺式存储

(2)基于链式存储

1.2折半插入排序

1.3希尔排序

2、交换排序

2.1冒泡排序

(1)基于顺式存储

(2)基于链式存储

2.2快速排序

3、选择排序

3.1简单选择排序

3.2堆排序

(1)大根堆

(2)小根堆

(3)堆的插入

(4)堆的删除

4归并排序

(1)基于顺式存储

(2)基于链式存储

5基数排序


#说明:以下所有排序算法(除了“堆排序”) 都是实现递增,数组下标从0开始。

1、插入排序

1.1直接插入排序

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 稳定
  • 可用于链式存储

(1)基于顺式存储

//插入排序
void InsertSort_in(int A[], int n) {
	int i, j, temp;
	for (i = 1; i < n; i++) {
			temp = A[i];
			for (j = i - 1; j >= 0 && A[j] > temp; j--) //为保证稳定性,不可以移动等于temp的元素
				A[j + 1] = A[j]; //从i左边的元素到所有比temp大的元素都需要右移
			A[j + 1] = temp;
	}
}

(2)基于链式存储

//基于带头结点的链式存储的直接插入排序
void InsertSort_L(LinkList L) {
	node *p = L->next;//p结点为待处理结点
	node *q = L;//q结点为p的前驱结点,用于恢复连接
	while (p) {
		node *s = L->next;//s前驱即为插入位置
		node *t = L;//保存s前驱
		while (s != p && s->data <= p->data) {
			/*
			寻找插入位置,为保证稳定性必须带上等号;
			注意此时while条件不能是s!=NULL,因为我们只需要判断待处理结点之前结点
			*/
			t = s;
			s = s->next;
		}
		q->next = p->next;//先把连接续上
		p->next = t->next;//把插入位置后继连上
		t->next = p;//把插入位置先驱连上

		//下面一行是方法一,这样会导致每次新处理的结点都从插入的位置开始,增加很多次无用循环
		//q = p; p = p->next;

		//下面五行是方法二,核心思想是直接处理q的后继结点,但需注意的是,q的后继也有可能是处理过的p,所以需要判断一下。
		if (p == q->next) {
			q = q->next; 
			p = q->next;
		}
		else p = q->next;
	}
	cout << count << endl;
}

1.2折半插入排序

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 稳定
  • 不可用于链式存储
//折半插入排序
void BiInsertSort(int A[], int n) {
	int i, j, temp, low, high, mid;
	for (i = 1; i < n; i++) {
		temp = A[i];
		low = 1; high = i - 1;
		while (low <= high) {
			mid = (low + high) / 2;
			if (A[mid] > temp)high = mid - 1;
			else low = mid + 1; //注意:为了保证稳定性,此时就算A[mid]==temp也查找右子表
		}
		for (j = i - 1; j >= high + 1; j--) //此时high+1就是low
			A[j + 1] = A[j];
		A[high + 1] = temp;
	}
}

1.3希尔排序

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 不稳定
  • 不可用于链式存储
//希尔排序
void ShellSort(int A[], int n) {
	int d, i, j, temp;
	for (d = n / 2; d >= 1; d/=2) {//首次步长为n/2,以后每减半,最后一轮即d=1,相当于直接插入排序
		for (i = d; i < n; i++) {
			temp = A[i];
			for (j = i - d; j >= 0 && temp < A[j]; j -= d)
				A[j + d] = A[j];//记录后移
			A[j + d] = temp;//退出循环时j已经减过d了,所以加回d才是插入的位置
		}
	}

2、交换排序

2.1冒泡排序

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 稳定
  • 可用于链式存储

(1)基于顺式存储

void swap(int &a, int &b) {
	int temp;
	temp = a;
	a = b;
	b = temp;
}
//冒泡排序
void BubbleSort(int A[], int n) {
	int i, j;
	for(i=0;i<n-1;i++){ //n个元素只需要n-1趟即可
		bool flag = false;
		for (j = 1; j < n - i; j++) {
			if (A[j-1] > A[j]) {//为保证稳定性,不能带上等号
				swap(A[j-1], A[j]);
				flag = true;
			}
		}
		if (flag == false) return;
	}
}

(2)基于链式存储

void swap_L(node* s) {//交换a指向的下一个元素和下下一个元素
	if (s == NULL)return;
	node *a = s->next;
	if (a == NULL)return;
	node *b = a->next;
	if (b == NULL)return;
	s->next = b;
	a->next = b->next;
	b->next = a;
}
//基于带头结点的链式存储的冒泡排序
void BubbleSort_L(LinkList L,int n) {
	node *p = L;
	node *s;
	for (int i = 0; i < n - 1;i++) {//遍历的趟数=结点数-1
		bool flag = false;
		for (s = L; s->next != NULL && s->next->next != NULL; s = s->next) {
			if (s->next->data > s->next->next->data) {
				swap_L(s);
				flag = true;
			}
		}
		if (flag == false) return;
	}
}

2.2快速排序

  • 时间复杂度:O(n²)
  • 空间复杂度:O(n)
  • 不稳定
  • 不可用于链式存储
//快速排序
void QuickSort(int A[],int low,int high){
	if (low < high) {
		//把A数组中随机一个元素和A[1]交换 //可以对快排进行优化
		int pivot = A[low];//选取第一个元素作为枢轴
		int n1 = low, n2 = high;
		while (n1 < n2) {
			while (n1 < n2&&A[n2] >= pivot)n2--;//两个while判断条件必须加上等号,否则会相互死循环
			A[n1] = A[n2];
			while (n1 < n2&&A[n1] <= pivot)n1++;
			A[n2] = A[n1];
		}
		A[n1] = pivot;
		QuickSort(A, low, n1 - 1);
		QuickSort(A, n1 + 1, high);
	}
}

3、选择排序

3.1简单选择排序

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 不稳定
  • 可用于链式存储
  • 需要对比关键字\frac{n(n-1)}{2}次;元素交换次数<n-1
//简单选择排序
void SelectSort(int A[], int n) {
	for (int i = 0; i < n - 1; i++) {//一共进行n-1趟
		int min = i;
		for (int j = i + 1; j < n; j++)
			if (A[j] < A[min])min = j;
		swap(A[i], A[min]);
	}
}

3.2堆排序

  • 时间复杂度:\small O(nlog{_2}n)
  • 空间复杂度:O(1)
  • 不稳定
  • 可用于链式存储
  • 每“下坠一层”,最多只需对比关键字2次;若树高为h,某结点在第i层,则将这个结点向下调整最多只需要“下坠”h-i层,关键字对比次数不超过2(h-i)
  • 其中建堆时间复杂度=O(n)

(1)大根堆

//大根堆
//将以k为根的子树调整为大根堆
void MaxHeapAdjust(int A[], int k, int len) {
	A[0] = A[k];
	for (int i = 2 * k; i <= len; i *= 2) {
		if (i < len && A[i] < A[i + 1]) i++;//若存在比左孩子大的右孩子,则把i调整为右孩子标号
		if (A[0] >= A[i]) break;//直接拜拜,不用再下坠了
		else {
			A[k] = A[i];
			k = i;//转化成调整以i为根的子树
		}
	}
	A[k] = A[0];
}
//建立大根堆
void BuildMaxHeap(int A[], int len) {
	for (int i = len / 2; i > 0; i--)
		MaxHeapAdjust(A, i, len);
}
//堆排序
void MaxHeapSort(int A[], int len) {
	BuildMaxHeap(A, len);
	for (int i = len; i > 1; i--) {
		swap(A[i], A[1]);//将根结点换到第i位
		MaxHeapAdjust(A, 1, i - 1);//待排元素减一,更新根结点
	}
}

(2)小根堆

//小根堆
void MinHeapAdjust(int A[], int k, int len) {
	A[0] = A[k];
	for (int i = 2 * k; i <= len; i *= 2) {
		if (i<len&&A[i]>A[i + 1])i++;
		if (A[0] <= A[i]) break;
		else {
			A[k] = A[i];
			k = i;
		}
	}
	A[k] = A[0];
}
void MinHeapSort(int A[],int len) {
	for (int i = len / 2; i > 0; i--)
		MinHeapAdjust(A, i, len);
	for (int i = len; i > 1; i--) {
		swap(A[i], A[1]);
		MinHeapAdjust(A, 1, i-1);
	}
}

(3)堆的插入

核心思想:插入到堆底,然后再不断“上升”,直到无法继续上升为止。

//插入值为x的结点-基于小根堆实现
void HeapInsert(int A[], int &len, int x) {
	int k = len+1; A[k] = x;
	A[0] = A[k];
	for (int i = k/2; i > 0; i /= 2) {
		if (A[0] >= A[i])break;
		else {
			A[k] = A[i];
			k = i;
		}
	}
	A[k] = A[0];
	len++;
}

(4)堆的删除

核心思想:被删元素用堆底元素代替,然后再不断“下坠”,直到无法继续下坠为止。

//删除编号为num的结点(数组下标从1开始)-基于小根堆实现
void HeapDelete(int A[], int &len, int num) {
	int k = num; A[k] = A[len];
	A[0] = A[k];
	for (int i = 2 * k; i <= len; i *= 2) {
		if (i<len&&A[i]>A[i + 1])i++;
		if (A[0] <= A[i]) break;
		else {
			A[k] = A[i];
			k = i;
		}
	}
	A[k] = A[0];
	len--;
}

4归并排序

  • 时间复杂度:\small O(nlog{_2}n)
  • 空间复杂度:O(n)【链式存储为O(1)】
  • 稳定
  • 可用于链式存储

(1)基于顺式存储

//归并排序
int *B = (int *)malloc(sizeof(int)*n);//辅助数组
void Merge(int A[], int low, int mid, int high) {
	int i, j, k;
	for (k = low; k <= high; k++)
		B[k] = A[k];//将待排序元素复制到辅助数组里
	for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++) {
		if (B[i] <= B[j]) A[k] = B[i++];
		else A[k] = B[j++];
	}
	while (i <= mid)A[k++] = B[i++];
	while (j <= high)A[k++] = B[j++];
}
void MergeSort(int A[], int low, int high) {
	if (low < high) {
		int mid = (low + high) / 2;
		MergeSort(A, low, mid);
		MergeSort(A, mid + 1, high);
		Merge(A, low, mid, high);
	}
}

(2)基于链式存储

//基于带头结点的链式存储的归并排序
LinkList Merge_L(LinkList l, LinkList r) {
	node *k, *L;
	if (l->data <= r->data) {
		L = k = l;
		l = l->next;
	}
	else {
		L = k = r;
		r = r->next;
	}
	while( l!= NULL && r != NULL) {
		if (l->data <= r->data) {
			k->next = l;
			l = l->next;
		}
		else {
			k->next = r;
			r = r->next;
		}
		k = k->next;
	}
	if (l != NULL)k->next = l;
	if (r != NULL)k->next = r;
	return L;
}
LinkList MergeSort_L(LinkList L) {
	if (L->next == NULL)return L;
	node *fast = L, *slow = L, *pre=NULL;
	while (fast != NULL && fast->next != NULL) {
		pre = slow;
		slow = slow->next;
		fast = fast->next->next;
	}
	pre->next = NULL;//必须把链表拆开,合并的时候再接上
	LinkList l = MergeSort_L(L);
	LinkList r = MergeSort_L(slow);
	return Merge_L(l, r);
}

5基数排序

  • 时间复杂度:O(d(n+r))【关键字由d元组组成,即d趟,每趟分配O(n),收集O(r)】
  • 空间复杂度:O(r)【r为最大基数】
  • 稳定
  • 可用于链式存储
  • 应用场景:①数据元素的关键字可以方便地拆分为d组,且d较小;②每组关键字的取值范围不大,即r较小;③数据元素个数n较大。
  • 例:①不适合给5个人的身份证号排序;②适合给10亿人的身份证号排序;③不适合给中文人名排序。
//基数排序
void RadixSort(int A[], int n) {
	queue<int> q[10];
	queue<int> L;
	for (int i = 0; i < n; i++)
		L.push(A[i]);
	int flag = 1;
	for (int x = 10;; x *= 10) {
		while (!L.empty()) {
			int k = L.front() % x / (x / 10);
			if (k != 0)flag = 0;
			q[k].push(L.front());
			L.pop();
		}
		for (int i = 0; i < 10; i++) {
			while (!q[i].empty()) {
				L.push(q[i].front());
				q[i].pop();
			}
		}
		if (flag == 1)break;
		flag = 1;
	}
	for (int i = 0; i < n; i++) {
		A[i] = L.front();
		L.pop();
	}
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值