计算机中的数制与排序算法详解
1. 数制系统概述
在计算机编程里,我们常常会和各种数制系统打交道,常见的有十进制、二进制、八进制和十六进制。下面为你详细介绍这些数制系统的特点:
-
十进制(Base 10)
:这是我们日常生活中最常用的数制,其数字范围是 0 到 9。最低位数字是 0,最高位数字是 9,也就是比基数 10 小 1。
-
二进制(Base 2)
:计算机内部采用的数制,仅有 0 和 1 两个数字。最低位数字是 0,最高位数字是 1,比基数 2 小 1。
-
八进制(Base 8)
:数字范围从 0 到 7,常用于简化二进制数的表示。
-
十六进制(Base 16)
:由于需要 16 个数字,除了 0 到 9 之外,还使用字母 A 到 F 来表示十进制的 10 到 15。最低位数字是 0,最高位数字相当于十进制的 15,比基数 16 小 1。
各数制系统的关键信息总结如下表:
| 数制系统 | 基数 | 数字范围 | 最低位数字 | 最高位数字 |
| ---- | ---- | ---- | ---- | ---- |
| 十进制 | 10 | 0 - 9 | 0 | 9 |
| 二进制 | 2 | 0, 1 | 0 | 1 |
| 八进制 | 8 | 0 - 7 | 0 | 7 |
| 十六进制 | 16 | 0 - 9, A - F | 0 | F(十进制 15) |
各数制系统中,每个数位的位置都有不同的位权值。例如,十进制数从右往左的位权值依次是 1、10、100、1000 等。下面是各数制系统中最右边四个位置的位权值表:
| 数制系统 | 位权值(从右往左) |
| ---- | ---- |
| 十进制 | 1, 10, 100, 1000 |
| 十六进制 | 1, 16, 256, 4096 |
| 二进制 | 1, 2, 4, 8 |
| 八进制 | 1, 8, 64, 512 |
2. 数制转换方法
不同数制之间的转换是编程中的常见操作,下面为你介绍一些常用的转换方法:
-
八进制转二进制
:把每个八进制数字替换成对应的三位二进制数。例如,八进制数 7 转换为二进制就是 111。
-
十六进制转二进制
:将每个十六进制数字替换成对应的四位二进制数。比如,十六进制数 F 转换为二进制是 1111。
-
其他数制转十进制
:把每个数字的十进制等效值乘以其位权值,然后将结果相加。例如,二进制数 1101 转换为十进制的计算过程为:(1\times2^3 + 1\times2^2 + 0\times2^1 + 1\times2^0 = 8 + 4 + 0 + 1 = 13)。
以下是一些具体的数制转换示例:
-
二进制 110101011000 转八进制和十六进制
:
- 转八进制:从右往左每三位一组,不足三位的在左边补 0,得到 011 010 101 100,对应的八进制数是 6530。
- 转十六进制:从右往左每四位一组,不足四位的在左边补 0,得到 1101 0101 1000,对应的十六进制数是 D58。
-
十六进制 FACE 转二进制
:F 对应 1111,A 对应 1010,C 对应 1100,E 对应 1110,所以结果是 1111 1010 1100 1110。
-
八进制 7316 转二进制
:7 对应 111,3 对应 011,1 对应 001,6 对应 110,结果是 111 011 001 110。
3. 负数的二进制表示:补码
计算机采用补码(Two’s Complement Notation)来表示负数。下面为你详细介绍补码的形成过程:
-
求反码(One’s Complement)
:使用 C 语言中的按位取反运算符
~
对二进制数的每一位进行取反操作,即 0 变为 1,1 变为 0。
-
求补码
:在反码的基础上加 1。
以十进制数 13 为例,其 32 位二进制表示是
00000000 00000000 00000000 00001101
,具体计算过程如下:
value:
00000000 00000000 00000000 00001101
~value (i.e., value’s ones complement):
11111111 11111111 11111111 11110010
Two’s complement of value:
11111111 11111111 11111111 11110011
补码表示负数的原理是,一个数和它的补码相加结果为 0。例如:
00000000 00000000 00000000 00001101
+11111111 11111111 11111111 11110011
------------------------------------
00000000 00000000 00000000 00000000
在减法运算中,计算机实际上是通过加上减数的补码来实现的,例如
x = a - value
可以转换为
x = a + (~value + 1)
。
4. 排序算法简介
排序是将数据按照一定的顺序(通常是升序或降序)排列的操作。常见的排序算法有选择排序、插入排序和归并排序。下面为你简单介绍这些算法:
-
选择排序(Selection Sort)
:简单但效率较低的排序算法。每次迭代选择剩余元素中的最小元素,然后与当前位置的元素交换。
-
插入排序(Insertion Sort)
:将未排序的数据插入到已排序序列的合适位置。
-
归并排序(Merge Sort)
:效率较高但实现较复杂的递归排序算法,采用分治法将数组分成两部分,分别排序后再合并。
下面是选择排序的 mermaid 流程图:
graph TD;
A[开始] --> B[初始化数组];
B --> C[外层循环,遍历数组长度 - 1 次];
C --> D[初始化最小元素索引为当前索引];
D --> E[内层循环,遍历剩余元素];
E --> F{当前元素是否小于最小元素};
F -- 是 --> G[更新最小元素索引];
F -- 否 --> E;
G --> H[交换最小元素和当前元素];
H --> I{外层循环是否结束};
I -- 否 --> C;
I -- 是 --> J[结束];
5. 算法效率:大 O 表示法
大 O 表示法(Big O Notation)用于估算算法的最坏情况运行时间,也就是算法解决问题所需的最大工作量。下面为你介绍几种常见的大 O 表示法:
-
O(1)
:常数时间复杂度,算法的运行时间与数据规模无关。例如,判断数组的第一个元素是否等于第二个元素,无论数组有多少个元素,都只需要一次比较。
-
O(n)
:线性时间复杂度,算法的运行时间与数据规模成正比。例如,判断数组的第一个元素是否等于其他任何元素,最多需要 n - 1 次比较。
-
O(n²)
:二次时间复杂度,算法的运行时间与数据规模的平方成正比。例如,判断数组中是否有重复元素,需要进行大约 (n^2/2 - n/2) 次比较。当数据规模较小时,O(n²) 算法的性能影响不明显,但随着数据规模的增大,性能会显著下降。
6. 选择排序算法实现
以下是使用 C 语言实现的选择排序算法代码:
// Fig. D.1: figD_01.c
// The selection sort algorithm.
#define SIZE 10
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// function prototypes
void selectionSort(int array[], size_t length);
void swap(int array[], size_t first, size_t second);
void printPass(int array[], size_t length, unsigned int pass, size_t index);
int main(void)
{
int array[SIZE]; // declare the array of ints to be sorted
srand(time(NULL)); // seed the rand function
for (size_t i = 0; i < SIZE; i++) {
array[i] = rand() % 90 + 10; // give each element a value
}
puts("Unsorted array:");
for (size_t i = 0; i < SIZE; i++) { // print the array
printf("%d ", array[i]);
}
puts("\n");
selectionSort(array, SIZE);
puts("Sorted array:");
for (size_t i = 0; i < SIZE; i++) { // print the array
printf("%d ", array[i]);
}
}
// function that selection sorts the array
void selectionSort(int array[], size_t length)
{
// loop over length - 1 elements
for (size_t i = 0; i < length - 1; i++) {
size_t smallest = i; // first index of remaining array
// loop to find index of smallest element
for (size_t j = i + 1; j < length; j++) {
if (array[j] < array[smallest]) {
smallest = j;
}
}
swap(array, i, smallest); // swap smallest element
printPass(array, length, i + 1, smallest); // output pass
}
}
// function that swaps two elements in the array
void swap(int array[], size_t first, size_t second)
{
int temp = array[first];
array[first] = array[second];
array[second] = temp;
}
// function that prints a pass of the algorithm
void printPass(int array[], size_t length, unsigned int pass, size_t index)
{
printf("After pass %2d: ", pass);
// output elements till selected item
for (size_t i = 0; i < index; i++) {
printf("%d ", array[i]);
}
printf("%d* ", array[index]); // indicate swap
// finish outputting array
for (size_t i = index + 1; i < length; i++) {
printf("%d ", array[i]);
}
printf("%s", "\n "); // for alignment
// indicate amount of array that is sorted
for (unsigned int i = 0; i < pass; i++) {
printf("%s", "-- ");
}
puts(""); // add newline
}
选择排序算法的效率是 O(n²),因为它包含两层嵌套的 for 循环。外层循环遍历数组长度 - 1 次,内层循环遍历剩余元素,每次迭代都需要比较和交换操作。
综上所述,掌握不同数制系统的表示和转换方法,以及常见排序算法的原理和实现,对于计算机编程至关重要。通过合理选择算法,可以提高程序的性能和效率。希望本文能帮助你更好地理解这些概念。
计算机中的数制与排序算法详解
7. 插入排序算法
插入排序是一种简单直观的排序算法,它的工作原理是将未排序数据插入到已排序序列的合适位置。以下是插入排序的详细步骤:
1. 从数组的第二个元素开始,将其视为未排序部分的第一个元素。
2. 取出该元素,在已排序序列中从后向前扫描。
3. 如果已排序序列中的元素大于取出的元素,则将该元素向后移动一位。
4. 重复步骤 3,直到找到一个小于或等于取出元素的位置。
5. 将取出的元素插入到该位置。
6. 重复步骤 1 - 5,直到整个数组排序完成。
插入排序的代码示例如下:
#include <stdio.h>
#define SIZE 10
void insertionSort(int array[], int length) {
for (int i = 1; i < length; i++) {
int key = array[i];
int j = i - 1;
while (j >= 0 && array[j] > key) {
array[j + 1] = array[j];
j = j - 1;
}
array[j + 1] = key;
}
}
int main() {
int array[SIZE] = {72, 34, 88, 14, 32, 12, 34, 77, 56, 83};
printf("Unsorted array:\n");
for (int i = 0; i < SIZE; i++) {
printf("%d ", array[i]);
}
printf("\n");
insertionSort(array, SIZE);
printf("Sorted array:\n");
for (int i = 0; i < SIZE; i++) {
printf("%d ", array[i]);
}
printf("\n");
return 0;
}
插入排序的效率也是 O(n²),因为在最坏情况下,每次插入操作都需要移动已排序序列中的所有元素。
8. 归并排序算法
归并排序是一种高效的排序算法,采用分治法的思想。它的基本步骤如下:
1.
分解
:将数组分成两个子数组,分别对这两个子数组进行排序。
2.
合并
:将两个已排序的子数组合并成一个有序的数组。
下面是归并排序的代码示例:
#include <stdio.h>
#define SIZE 10
void merge(int array[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int L[n1], R[n2];
for (int i = 0; i < n1; i++)
L[i] = array[left + i];
for (int j = 0; j < n2; j++)
R[j] = array[mid + 1 + j];
int i = 0;
int j = 0;
int k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
array[k] = L[i];
i++;
} else {
array[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
array[k] = L[i];
i++;
k++;
}
while (j < n2) {
array[k] = R[j];
j++;
k++;
}
}
void mergeSort(int array[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(array, left, mid);
mergeSort(array, mid + 1, right);
merge(array, left, mid, right);
}
}
int main() {
int array[SIZE] = {72, 34, 88, 14, 32, 12, 34, 77, 56, 83};
printf("Unsorted array:\n");
for (int i = 0; i < SIZE; i++) {
printf("%d ", array[i]);
}
printf("\n");
mergeSort(array, 0, SIZE - 1);
printf("Sorted array:\n");
for (int i = 0; i < SIZE; i++) {
printf("%d ", array[i]);
}
printf("\n");
return 0;
}
归并排序的效率是 O(n log n),因为每次分解操作将数组分成两部分,递归深度为 log n,而每次合并操作的时间复杂度是 O(n)。
9. 不同排序算法的比较
不同的排序算法在时间复杂度、空间复杂度和稳定性等方面存在差异。以下是选择排序、插入排序和归并排序的比较表格:
| 排序算法 | 时间复杂度 | 空间复杂度 | 稳定性 |
| ---- | ---- | ---- | ---- |
| 选择排序 | O(n²) | O(1) | 不稳定 |
| 插入排序 | O(n²) | O(1) | 稳定 |
| 归并排序 | O(n log n) | O(n) | 稳定 |
从表格中可以看出,归并排序的时间复杂度最低,在处理大规模数据时性能更好,但它需要额外的空间来合并子数组。选择排序和插入排序的实现简单,但时间复杂度较高,适用于小规模数据。
10. 排序算法的应用场景
不同的排序算法适用于不同的应用场景,以下是一些常见的应用场景和对应的推荐算法:
-
小规模数据
:选择排序和插入排序实现简单,对于小规模数据可以快速完成排序。
-
大规模数据
:归并排序的时间复杂度较低,适合处理大规模数据。
-
对空间要求高
:选择排序和插入排序的空间复杂度为 O(1),只需要常数级的额外空间。
-
需要稳定排序
:插入排序和归并排序是稳定的排序算法,适合对稳定性有要求的场景。
11. 总结
本文详细介绍了计算机中不同数制系统的表示和转换方法,以及常见排序算法的原理和实现。数制系统包括十进制、二进制、八进制和十六进制,掌握它们之间的转换方法对于理解计算机内部数据表示至关重要。排序算法方面,选择排序简单但效率低,插入排序适用于小规模数据,而归并排序在大规模数据处理中表现出色。同时,大 O 表示法可以帮助我们评估算法的效率,从而选择合适的算法来提高程序的性能。
在实际应用中,我们应根据数据规模、空间要求和稳定性等因素选择合适的排序算法。希望通过本文的介绍,你能对这些概念有更深入的理解,并在编程中灵活运用。
以下是归并排序的 mermaid 流程图:
graph TD;
A[开始] --> B[分解数组];
B --> C{数组长度是否为 1};
C -- 是 --> D[返回数组];
C -- 否 --> E[分成左右子数组];
E --> F[递归排序左子数组];
E --> G[递归排序右子数组];
F --> H[合并左右子数组];
G --> H;
H --> I[结束];
通过合理选择算法和数据表示方式,我们可以优化程序的性能,提高开发效率。希望大家在实际编程中能够灵活运用这些知识,解决遇到的问题。
超级会员免费看
139

被折叠的 条评论
为什么被折叠?



