0%

参考:

  1. bilibili:直接选择排序、冒泡排序、希尔排序、快速排序手推步骤
  2. bilibili:五分钟学会希尔排序【中英字幕】
  3. bilibili:插入排序(直接插入、希尔排序)算法思想

插入排序

插入排序就是在之前排好的有序表中找到合适位置插入。

例如一个乱序的数组:
{51,28,38,86,70,90,7,30,40,86,25}

  1. 第一个是51,此时还没有有序表,51作为有序表中的第一个。
  2. 28小于51,排在51前边{28,51}
  3. 38大于28小于51,排在28和51中间。{28,38,51}
  4. 86大于51,排在最后{28,38,51,86}

    直到排序完成。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
void Sort(int arr[],int sz);
int main()
{
    int arr[] = {51,28,28,86,-70,90,7,28,40,86,25};
    int sz = sizeof(arr)/sizeof(arr[0]);
    Sort(arr,sz);
    return 0;
}
void Sort(int arr[],int sz)
{
    int i;
    for (int i = 1; i < sz; i++) {
           int key = arr[i];
           int j = i - 1;
           // 找到 key 的插入位置
           while (j >= 0 && arr[j] > key) {
               arr[j + 1] = arr[j];
               j--;
           }
           arr[j + 1] = key; // 插入 key
       }
}

直接选择(简单)排序

例如一个数组:
{51,28,38,86,70,90,7,30,40,86,25}
先找到这个数组中最小的数字,也就是7,把它和第一个数字,51交换位置,数组变成了:
{(7),28,38,86,70,90,51,30,40,86,25}
再从这个数组的第二个数字开始寻找最小的数字,也就是25,和第二个数字28交换位置:
{(7,25),38,86,70,90,51,30,40,86,28}

直到排序完成。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
void Sort(int arr[],int sz);
int main()
{
    int arr[] = {51,28,28,86,-70,90,7,28,40,86,25};
    int sz = sizeof(arr)/sizeof(arr[0]);
    Sort(arr,sz);
    return 0;
}
void Sort(int arr[],int sz)
{
    int i = 0;
    int j = 0;
    for (j=0; j<sz; j++)
    {
        int minValue = arr[j];
        int minIndex = j;
        for (i=j+1; i<sz; i++)//找到最小的数字
        {
            if (arr[i] < minValue)
            {
                minValue = arr[i];//更新最小的数字
                minIndex = i;//最小数字的位置
            }
        }
        if (minIndex != j)//只在必要时交换元素
        {
            int temp = arr[j];
            arr[j] = minValue;
            arr[minIndex] = temp;
        }
    }
}

冒泡排序

假设一个数组:
{51,28,38,86,70,90,7,30,40,86,25}
51先与28进行比较,51>28,数组变成
{28,51,38,86,70,90,7,30,40,86,25}
51再与38比较,51>38,数组变成
{28,38,51,86,70,90,7,30,40,86,25}

直至第一轮排序完成,数组变为:
{28,38,51,70,86,7,30,40,86,25,(90)}
可以看到,经过了第一轮排序,把最大的数字移到数组的末尾,之后不会再对这个数字进行排序。

再次进行第二轮冒泡排序:
{28,38,51,70,86,7,30,40,86,25,(90)}
28与38比较,28<38,数组不变。
38再与51比较,38<51,数组不变。

86与7比较,86>7,数组改为:
{28,38,51,70,7,86,30,40,86,25,(90)}
86与30比较,数组变成:
{28,38,51,70,7,30,86,40,86,25,(90)}
86与40比较,数组变成:
{28,38,51,70,7,30,40,86,86,25,(90)}
下次比较时,86与86相等,顺序不变,然后86再与25比较:
{28,38,51,70,7,30,40,86,25,86,(90)}
经过第二轮冒泡排序,数组变成:
{28,38,51,70,7,30,40,86,25,(86,90)}
下次再排序时,后两位不会再修改。

直到排序完毕。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
void Sort(int arr[],int sz);
int main()
{
    int arr[] = {51,28,28,86,-70,90,7,28,40,86,25};
    int sz = sizeof(arr)/sizeof(arr[0]);
    Sort(arr,sz);
    return 0;
}
void Sort(int arr[],int sz)
{
    int i = 0;
    int j = 0;
    int swapped;//发生交换标记
    for (j=0; j<sz-1; j++)//趟数
    {
        swapped = 0; // 标记这一趟是否有交换
        for (i=0; i<sz-j-1; i++)//一趟冒泡排序
        {
            if (arr[i] > arr[i+1])
            {
                int temp = arr[i];
                arr[i] = arr[i+1];
                arr[i+1] = temp;
                swapped = 1; // 有交换发生
            }
        }
        // 如果没有交换,提前结束
        if (!swapped)
        {
            break;
        }
    }
}

希尔排序

希尔排序首先需要一个步长,这个步长不是固定的,根据实际情况调整。
在这里我们取步长为d=(5,3,1)。
假设一个数组:
{51,28,38,86,70,90,7,30,40,86,25}
步长为5,即51和90为一组,28和7为一组,38和30为一组,86和40为一组,70和86为一组,25单独为一组,本次不对其排序。
进行第一趟排序,不同组进行分别比较:

  1. 51<90,这组顺序不改变
  2. 28>7,把28和7调换位置
  3. 38>30,调换位置
  4. 86>40,调换位置
  5. 70<86,不变
  6. 25单独一组,不变
    进行第一趟排序后,数组变为:
    {51,7,30,40,70,90,28,38,86,86,25}

再以步长为3,在第一次排序后的基础上,进行第二趟希尔排序:
{51,7,30,40,70,90,28,38,86,86,25}
51和40为一组,7和70为一组,30和90为一组,28和86为一组,38和25为一组,倒数第三个数86单独一组。

  1. 51>40,调换位置
  2. 7<70,不变
  3. 30<90,不变
  4. 28<86,不变
  5. 38>25,调换位置
  6. 86单独一组,位置不变
    按照上面进行第二趟希尔排序:
    {40,7,30,51,70,90,28,25,86,86,38}

再以步长为1,进行第三趟希尔排序:

  1. 40先和7比较,调换位置
    {7,40,30,51,70,90,28,25,86,86,38}
  2. 40和30比较,调换位置
    {7,30,40,51,70,90,28,25,86,86,38}
  3. 40和51比较,不变
  4. 51和70比较,不变
  5. 70和90比较,不变,90和28比较,调换位置

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
void Sort(int arr[],int sz);
int main()
{
    int arr[] = {51,28,28,86,-70,90,7,28,40,86,25};
    int sz = sizeof(arr)/sizeof(arr[0]);
    Sort(arr,sz);
    return 0;
}
void Sort(int arr[],int sz)
{
    int gap,i,j,temp;
    for (gap = sz / 2; gap > 0; gap = gap / 2)//先定义步长
    {
        for (i = gap; i < sz; i++)//元素间移动位置
        {
            for (j = i - gap; j>=0 && arr[j]>arr[j+gap]; j = j - gap)//比较各相距离gap个位置的元素
            {
                temp = arr[j];//更换位置
                arr[j] = arr[j+gap];
                arr[j+gap] = temp;
            }
        }
    }
}

快速排序

先选出一个节点(取有意义的,或者取第一个),把这个节点放在一个数组的中间,把要排序的数字中小于它的放在它的左侧,右侧都是大于它的数字,最后对两边的数字进行排序。
假设一个数组
{51,28,38,86,70,90,7,30,40,86,25}
选取数组中第一个数字作为节点P,最左边为L指针,最右边为R指针。

512838867090730408625
⬆️P
⬆️L⬆️R

P节点在最左边,要先从最右边的R指针开始比较,由于25<51,把25放在左边。⬇️

512838867090730408625
⬆️P
⬆️L⬆️R
25

接下来L指针右移,由于28<51,把28放在左边,L指针右移。⬇️

512838867090730408625
⬆️P
⬆️L⬆️R
2528

L指针指向的38<51,把38放在左边,L指针右移。⬇️

512838867090730408625
⬆️P
⬆️L⬆️R
252838

L指针指向的86>51,86去R指针所在位置,接下来左移R指针。⬇️

512838867090730408625
⬆️P
⬆️L⬆️R
25283886

R指针指向的86>51,86放在右边,R指针左移。⬇️

512838867090730408625
⬆️P
⬆️L⬆️R
2528388686

R指针指向的40<51,40移动到L指针所在的位置,接下来右移L指针。⬇️

512838867090730408625
⬆️P
⬆️L⬆️R
252838408686

L指针指向的70>51,70移动到R指针所在的位置,接来下左移R指针。⬇️

512838867090730408625
⬆️P
⬆️L⬆️R
25283840708686

R指针指向的30<51,30移动到L指针所在的位置,接下来右移L指针。⬇️

512838867090730408625
⬆️P
⬆️L⬆️R
2528384030708686

L指针指向的90>51,90移动到R指针所在位置,接下来左移R指针。⬇️

512838867090730408625
⬆️P
⬆️L⬆️R
252838403090708686

R指针指向的7<51,7移动到L指针位置,接下来右移L指针,L和R指针汇合,把P指针(51)移动到这里。⬇️

512838867090730408625
⬆️R⬆️L
252838403075190708686
⬆️P

接下来把P指针左侧和右侧作为两个单独的数组,再次排序,直到数组有序。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
int partition(int arr[], int low, int high) {
    int pivot = arr[high]; // 选择最后一个元素作为基准
    int i = (low - 1); // 小于基准的元素索引
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++;
            swap(&arr[i], &arr[j]); // 交换
        }
    }
    swap(&arr[i + 1], &arr[high]); // 将基准放到正确位置
    return (i + 1);
}
void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high); // 找到基准元素的索引
        quickSort(arr, low, pi - 1);  // 对基准左侧进行排序
        quickSort(arr, pi + 1, high); // 对基准右侧进行排序
    }
}
void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
int main() {
    int arr[] = {51, 28, 38, 86, 70, 90, 7, 30, 40, 86, 25};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("原数组: ");
    printArray(arr, n);
    quickSort(arr, 0, n - 1);
    printf("排序后数组: ");
    printArray(arr, n);
    return 0;
}

编写一个函数invert(x,p,n),该函数返回对x执行下列操作后的结果值:将x中从第p位开始的第n个(二进制)位求反(即1变成0,0变成1),x的其余各位保持不变。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int invert(unsigned x,int p,int n);
int main()
{
    printf("%d",invert(311,3,));
    return 0;
}

int invert(unsigned x,int p,int n)
{
    return x ^ (~(~0 << n) << (p+1-n));
}

题目

编写一个函数setbits(x,p,n,y),该函数返回对x执行下列操作后的结果值:将x中从第p位开始的n个(二进制)位设置为y中最右边n位的值,x的其余各位保持不变。

分析

例如:setbits(200,5,2,195)
200的二进制数是:1100 1000
这个二进制数的的第p位,开始的第n个二进制位。也就是第5位开始数2位(从右往左数)。
即:1100 1000,加粗的10就是要替换的。
替换为195的二进制数最右边2位的值:1100 0011
也就是将00替换为11,即替换后的x为1111 1000,即十进制的248。

位操作符(按位与、按位或、按位异或)
移位操作符(左移、右移)

代码

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
unsigned setbits(unsigned int x,int p,int n,unsigned int y);
int main()
{
    printf("%d",setbits(200, 5, 2, 195));
    return 0;
}
unsigned int setbits(unsigned int x,int p,int n,unsigned int y)
{
    return x & ~(~(~0 << n) << (p+1-n)) | (y & ~(~0 << n)) << (p+1-n);
}

(~0 << n) 先把0按位取反,变成二进制位全都是1的二进制数,即1111 1111,再向右移n位(2位),变成1111 1100。

~(~0 << n) 把上面获得的1111 1100 再次按位取反,得到0000 0011。

~(~0 << n) << (p+1-n) 把0000 0011右移p+1-n位,即4位,得到0011 0000。

~(~(~0 << n) << (p+1-n)) 再次按位取反,得到 1100 1111。

x & ~(~(~0 << n) << (p+1-n))x与得到的二进制数进行按位与操作,有0则0,全1为1。得到
1100 1000。

(y & ~(~0 << n)) y与0000 0011进行按位与操作,得到0000 0011。

(y & ~(~0 << n)) << (p+1-n) 把0000 0011右移4位,得到0011 0000。

x & ~(~(~0 << n) << (p+1-n)) | (y & ~(~0 << n)) << (p+1-n)0011 0000和1100 1000进行按位与操作,得到1111 1000,十进制就是248。


編写函数any(s1,s2),将字符串s2中的任一字符在字符串s1中第一次出现的位置作为结果返回。如果s1中不包含s2中的字符,则返回-1。(标准库函数strpbrk具有同样的功能,但它返回的是指向该位置的指针。)

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
int any(char* s1,char* s2)
{
    int i = 0;
    int j = 0;
    int ret = 1;
    for (i=0; s1[i]!='\0'; i++)
    {
        for (j=0; s2[j]!='\0'; j++)
        {
            if (s1[i] == s2[j])
            {
                return ret;
            }
        }
        ret++;
    }
    return -1;
}
int main() {
    char s1[10] = "fwehqasceq";
    char s2[3] = "ca";
    int ret = any(s1,s2);
    printf("%d\n",ret);
    return 0;
}

重新编写函数squeeze(s1,s2),将字符串s1中任何与字符串s2中的字符匹配的字符都删除。(把匹配的字符替换为’\0’)
代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
void squeeze(char* s1,char* s2)
{
    int i = 0;
    int j = 0;
    for (i=0; s1[i]!='\0'; i++)
    {
        for (j=0; s2[j]!='\0'; j++)
        {
            if (s1[i] == s2[j])
            {
                s1[i] = 0;
                break;
            }
        }
    }
}
int main() {
    char s1[10] = "abcdefghi";
    char s2[3] = "bd";
    squeeze(s1,s2);
    return 0;
}

编写函数htoi(s),把由十六进制数字组成的字符串(包含可选的前缀0x或0X)转换为与之等价的整型值。字符串中允许包含的数字包括:09、af、A~F。
代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <math.h>
int hosi(char input[])
{
    int i = 0;
    int j = 0;
    int x = 0;
    int a = 0, b = 0, c = 0;
    static int sum = 0;
    for (i=0; input[i+1]!='\0'; i++)
    {
        ;//计算出输入的十六进制数的位数
    }
    for (j=i; j>=2; j--)
    {
        if (input[j] >='0' && input[j] <='9') {
            a = input[j] - '0';
        }
        if (input[j] >='a' && input[j] <='f') {
            a = input[j] - 'a' + 10;
        }
        if (input[j] >='A' && input[j] <='F') {
            a = input[j] - 'A' + 10;
        }
        for (x = 0; x<=c ;x++)
        {
            b = a * pow(16,x);
        }
        c++;
        sum = sum + b;
    }
    return sum;
}
int main()
{
    char input[20] = {0};
    scanf("%s",&input);
    int ret = hosi(input);
    printf("%d\n",ret);
    return 0;
}

上面的代码中,对输入的十六进制数字的每一位分三种情况处理:

  1. 这一位是0-9:直接用这个字符减去’0’;
  2. 这一位是a-f:用这一位减去字符’a’再加10;
  3. 这一位是A-F:用这一位数字减去’A’再加10。
    用上面的方式处理,是由于ASCII码表中各字符是按照一定顺序排列的。

《C程序设计语言》练习2-2

1
2
    for (i=0; i<lim-1 && (c=getchar()) != '\n' && c != EOF ; ++i)
        s[i] = c;

在不使用运算符&&或||的条件下编写一个与上面的for循环语句等价的循环语句。

先分析给出的原代码:

  1. 初始化 i = 0
  2. 检查循环条件:
    • i < lim - 1:确保数组的索引不超出界限。
    • (c = getchar()) != '\n':从输入中读取字符,判断是否是换行符。
    • c != EOF:判断是否到达文件尾。
  3. 如果所有条件都为真,则执行循环体 s[i] = c,将字符 c 存入数组 s[i] 中。
  4. 每次循环结束后,执行 ++i,然后重新检查所有条件。

特点:

  • 条件检查非常紧凑,所有条件是“与”的关系(&&)。
  • 一旦任何一个条件不满足,循环立即终止。
  • 如果 getchar() 读到换行符或文件尾(EOF),整个循环停止,且不会再执行 s[i] = c

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    for (i=0; i<lim-1; ++i) 
    {
        if ((c=getchar()) != '\n')
        {
            if (c != EOF)
            {
                s[i] = c;
            }
            else
            {
                break;
            }
        }
        else
        {
            break;
        }
    }

注意:在使用if判断c=getchar()) != '\n'c != EOF时一定要加else来退出这个for循环,因为题目中循环使用&&的短路特性,当任一条件为假时就停止。
如果不加上break退出循环,虽然结果可能是一致的,但逻辑并不相等。

《C程序设计语言》练习2-1
编写一个程序以确定分别由signed及unsigned限定的char、short、int及long类型变量的取值范围。采用打印标准头文件中的相应值以及直接计算两种方式实现。后一种方法的实现较困难一些,因为要确定各种浮点类型的取值范围。

思路:

  1. 先把数字0的二进制位进行按位取反,也就是都变成1。
  2. 再把这个二进制数的值转换为unsigned型,并右移一位来去掉符号位。
  3. 最后转换为有符号型。
    相关:
  4. 按位取反~
  5. 移位操作符
  6. 强制类型转换
  7. 原码、反码、补码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include <stdio.h>
    #include <limits.h>
    int main()
    {
        //有符号型
        printf("signed char min = %d\n",-(char)((unsigned char) ~0 >> 1)-1);
        printf("signed char max = %d\n",(char)((unsigned char) ~0 >> 1));
        printf("signed short min = %d\n",-(short)((unsigned short) ~0 >> 1)-1);
        printf("signed short max = %d\n",(short)((unsigned short) ~0 >> 1));
        printf("signed int min = %d\n",-(int)((unsigned int) ~0 >> 1)-1);
        printf("signed int max = %d\n",(int)((unsigned int) ~0 >> 1));
        printf("signed long min = %ld\n",-(long)((unsigned long) ~0 >> 1)-1);
        printf("signed long max = %ld\n",(long)((unsigned long) ~0 >> 1));
        //无符号型
        printf("unsigned char max = %u\n",(unsigned char) ~0);
        printf("unsigned short max = %u\n",(unsigned short) ~0);
        printf("unsigned int max = %u\n",(unsigned int) ~0);
        printf("unsigned long max = %lu\n",(unsigned long) ~0);
        return 0;
    }
    以上面的char型为例,char型是由8位二进制数组成的:
    (char)((unsigned char) ~0 >> 1)
    ~0把数字0进行按位取反,也就是把0000 0000变为1111 1111。
    再把~0转换为无符号型:(unsigned char) ~0,这一步是为了下面的右移操作。
    把这个无符号型的二进制数右移,去掉符号位。即0111 1111:(unsigned char) ~0 >> 1
    最后,把unsigned char型转换为char型,也就是signed char型:(char)((unsigned char) ~0 >> 1)

对于char型的最小值,要在前面最大值的基础上取反再减1。

char、signed char和unsigned char的区别

在C标准中没有规定char是signed char还是unsigned char,主要取决于编译器。
但一般而言,未指定的数据类型均为signed,如int、char等价于signed int、signed char。
如果想要定义无符号类型,需要在前面加上unsigned。

为什么最小值要在最大值的基础上取反再减1?

以char型为例,就是一个占8位的二进制数,去掉符号位,数值位还有7位二进制数。
0-127的二进制表示是从00000000~01111111,全都是0开头的,0是代表正数的符号位,而-128~-1的补码表示为10000000-11111111,全是1开头的,所以1是代表负数的符号位。

GB_tpl001.ept 模版(图框、表格、符号库)国标;内置中国的标准,GB。

GOST_tpl001.ept 模版 俄罗斯标准。

IEC_tpl001.ept 国际电工委员会,国际标准。

Inch_tpl001.ept 日本工业委员会标准。

NFPA_tpl001.ept美国消防协会标准。

Num_tpl001.ept 国际电工委员会标准,按顺序编号。

项目结构

功能面结构
描述系统的作用,在eplan用‘=’表示,高层代号。
位置面结构
系统的位置,定位,用‘+’表示,位置代号。
产品面结构
系统的构成,用‘-’表示。

  • 取模运算的本质是求除法的余数
  • 余数 = 被除数 - (除数 × 商)
    例如,下面的程序可以求出一个三位数字的每一位数字,并存储在一个单独的寄存器中。
    求模
    在这个程序中,三位数字存储在D0中,个、十、百位数字分别存储在D1、D2、D3中。
    上面的程序只能处理正数,对于负数求模,应该先得到这个负数的相反数,再对这个相反数按照上面的方式求模,最后将得到的模再求相反数,就得到了这个负数的模。