C语言程序与设计
1.1基础知识
1.1计算机语言
计算机语言有3种,机器语言(1010100..)、低级语言(汇编语言)、高级语言(C语言、Java、...)
01序列中的每一位都是一个比特(bit),8比特为一个字符(B)
1.2 进制转换
R多项求和,十 除R取余
[破产] R进制转十: 位数×进制^(...1,0) 之和
[购买] 十转R进制: 除R取余,向上取
技巧
:快速将二进制转为十进制
算法与普通R转十进制一样,就是记住二进制的一系列数来免去计算,即:... 256 128 64 32 16 8 4 2 1 (0位除外,求其和)
1.3 main 函数
C程序由函数构成,C语言规定必须用main作为主函数名且只有一个main函数,程序将从此函数开始执行,在此函数结束。
1.4 C语言编译
源程序(.c) > 编译 > 目标文件(.obj) > 链接 > 可执行文件(.exe)
1.5 编译预处理
编译预处理包括:文件包含(#include ...)、宏定义(#define 名称 内容 )、条件编译
注意文件包含是可嵌套的:在一个被包含文件中可以包含另一个被包含文件。
在编译系统对C源程序进行预处理时,用<内容>直接替换标识符 <名称>
例题
:若有宏定义:#define TEST(y) y*y,则表达式TEST(4-2)的值为 ?
TEST(4-2)被替换为4-2*4-2=4-8-2=-6。
1.6 C语言的数据类型
C语言的数据类型有三种:整型(整数)、实型(小数/浮点数)、字符型
占用字符大小:
#include <stdio.h>
int main() {
printf("%d\n", sizeof(int));
printf("%d-%d\n", sizeof(float ),sizeof(double));
printf("%d\n", sizeof(char));
return 0;
}
输出:
4
4-8
1
注意:如何想表示字符常量用单引号如,'6'、'h'。如果想表示字符串常量则用双引号,如"Hello world"
注意:'\n' 表示的是一个回车符,表示的是一个字符
1.7 C语言数据类型的声明
int a = 22; #整型
float b = 2.2; #float实型
double c = 3.3; #double实型(默认实型类型)
char c = 'c'; #字符常量
char *str = "hello world!"; #字符串常量
1.8 数据占用说明
整型:sizeof(22) 占4个字节(B)
默认实型:sizeof(22.2) 默认是double实型,占8个字节(B)
字符常量:char 占用4个字节
字符串常量:char*
第一个“双撇号”中什么都不写,即"" (则只有 '\0' 一个字符),所以只占一字节。
第二个"a"中有 1 个可见字符,占 2 字节。
第三个"CHINA"有 5 个可见字符,占 6 字节。
第四个"How are you"中,空格也是字符,也算是可见的,所以总共有 11 个可见字符,共占 12 字节。
第五个"I love you"共 10 个可见字符,占 11 字节。
第六个"你好"为什么占 5 字节?有 2 个可见字符不是应该占3字节吗?C 语言规定,1 个英文字符占 1 字节,而 1个 中文字符占 2 字节,就算是中文的标点符号也是占 2 字节。所以两个汉字占 4 字节,加上 '\0' 总共是 5 字节。
让整数占用更少的内存可以在 int 前边加 short,让整数占用更多的内存可以在 int 前边加 long,例如:
short int a = 10;
short int b, c = 99;
long int m = 102023;
long int n, p = 562131;
这样 a、b、c 只占用 2 个字节的内存,而 m、n、p 可能会占用 8 个字节的内存。
1.9 数据的输出
d 以十进制形式输出带符号整数(正数不输出符号)
o 以八进制形式输出无符号整数(不输出前缀0)
x,X 以十六进制形式输出无符号整数(不输出前缀Ox)
u 以十进制形式输出无符号整数
f 以小数形式输出单、双精度实数
e,E 以指数形式输出单、双精度实数
g,G 以%f或%e中较短的输出宽度输出单、双精度实数
c 输出单个字符
s 输出字符串
p 输出地址
2.0 C语言的结束标符
14.在C语言中,以 '\0' 作为字符串结束的标
2.1 运算符的优先级
1.括号运算符:()
2.算术运算符:+,-,,/,%,++,--**3.关系运算符:<,>,<=,>=,==,!=
4.逻辑运算符:&&(与),||(或),!(非)
5.复合运算符:+=,-=, =,/=,%=
优先级:规定了运算的先后顺序。
算术>关系>逻辑
下面输出了哈?
#include <stdio.h>
int main() {
printf("%d\n",(1+4*2%3+1/4));
return 0;
}
(你需要掌握 1+4*2%3+1/4 转为 1+(4*2%3) + (1/4) = 1+2+0 = 3 )
2.3猜猜运算结果
printf("%f",2/3*2.1*2.1);
输出: 0.000000
因为2/3 = 0
2.4 判断是否为闰年
2.5 逗号运算符
int a=1,b=2,则(a+3,b=a+b,b+5,a+b) 的值是4
需要掌握,a+3不在最后一个是无意义的,b=a+b才是有意义的,且返回的是最后一个运算的值即是最后一个的a+b
#include <stdio.h>
int main() {
int a = 1;
int b = 2;
int c = 0;
printf("%d\n",(a+b,b = a+b,c = a+b));
printf("%d",c);
return 0;
}
输出:
4
4
2.6输入不符合的类型
当键盘输入时,对于输入整型变量也是可以输入实型变量的,但小数部分不能被存储。
2.7 非0,1的关系或逻辑运算
#include <stdio.h>
int main() {
printf("%d\n", (!-1));
return 0;
}
0才代表false,
2.8 输出指定小数个数
int main() {
double a = 12.34567;
printf("%.2f", a);
return 0;
}
输出:12.34
%.2f 输出2位小数
3.8 ASCII 间接输出某个字符
#include <stdio.h>
int main() {
printf("%c", (char)('F'-1));
return 0;
}
3.9 当输入char 字符型
#include <stdio.h>
int main() {
int a,b;
float c;
scanf("%d%c%f",&a,&b,&c);
printf("%d,%d,%f\n",a,b,c);
return 0;
}
输入:1d2m2
输出:1,100,2.000000
由上可知,数据是从左向右取的。注意输入要我们要scanf的第一个参数,然后再强转为右边。
空格或回车隔开,或两者混合都可以正确输入
不能使用“空格”进行隔开输入。
注意输入要我们要scanf的第一个参数,然后再强转为右边。
4.0 输出%d,%f 要严格使用,否则会输出错误的结果
int a =3;
float b = 347.63692;
printf("%f,%d",a,b);
输出:
-26815615859885212000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000.000000,1077387735
4.1 else 的配对
#include <stdio.h>
int main() {
if(0)
if(1)
printf("11");
else if(0)
printf("22");
else
printf("33");
else
printf("44");
return 0;
}
else 总是与还没有配对(最近的if还没有else与之配对,else if 不算,else才是代表一个if的结束)的if 进行配对,else if
4.2嵌套三目的运算解析
设a=1,b=2,c=3,d=4,则条件表达式a<b?a:c<d?c:d的值是?
a>b?a:(c<d?c:d)
从左到右看作从父到子。
4.3 0也是偶数
0%2 == 0 为true所以0也是偶数。
4.4 字符串的结束符是'\0'
计算英文字母、数字与其它字符的数量。
str[i] == '\0时,代表遍历字符串已经结束了。
#include <stdio.h>
#include <math.h>
int main() {
//程序任务:输入一串字符,计算英文字母、数字与其它字符的数量
char str[20];
scanf("%s",&str);
int en = 0;
int number = 0;
int no = 0;
for (int i = 0; str[i] != '\0'; i++ ) {
char current = str[i];
if((current >= 'A' && current <= 'Z') || (current >= 'a' && current <= 'z')) en++;
else if (current >= '1' && current <= '9') number++;
else no++;
}
printf("%d\n%d\n%d", en,number,no);
return 0;
}
4.5等差数列的和在程序中不用公式算
i += d;即可
4.6什么是水仙花数
13+33+5^3=153 各位的立方和等于自己的数叫水仙花数
4.7 什么是质数
2 ≤ j ≤ (int)sqrt(k)
k%6 ≠1或k%6 ≠5时不是质数
什么是质数:一个大于1的自然数,且除了1和它本身外,不能被其他自然数整除的数。
#include <stdio.h>
#include <math.h>
int main()
{
int min = 10;
int max = 1000000000;
for (int i = min; i <= max; ++i) {
if (i%6 != 1 && i%6 != 5) continue;
int r = (int)sqrt(i);
for (int j = 2; j <= r; ++j) {
if(i%j == 0) break; //不是质数
else if(j == r-1) {
//在这里就满足了质数的条件
printf("%d,",i);
}
}
}
}
4.8 大小写英文字母的差值
printf("\n%c",'a'-32);
输出A
相差32,大写 < 大写
4.8 专业的字符串输入与输出函数
#include <stdio.h>
#include <math.h>
#include <string.h>
int main()
{
char *a;
gets(a);
puts(a);
return 0;
}
输入:
论
输出:
论
4.9 几个字符串处理函数
1.strcmp(str1,str2)比较str1和str2的大小。 比较字符串的已存放字符的长度
2.strcpy(str1,str2)把str2复制(copy)到str1中。
3.strcat(str1,str2)把str2连接到str1后面。
4.strlen(str)计算str中的非'\0'字符个数。
5.使用时要加上#include<string.h>6.注意:计算时,以'\0'为字符串结束标志。
5.0 有内涵冒泡排序
错位!第一个错位点 j = i+1 第二个错位点是 +1末位
#include <stdio.h>
#include <math.h>
#include <string.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
for (int k = 0; k < 10; ++k) {
scanf("%d",&arr[k]);
}
//从大到小
for (int i = 0; i < 9; ++i) {
for (int j = i+1; j < 10; ++j) {
if(arr[i] < arr[j]) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
for (int z = 0; z < 10; ++z) {
printf("%d,",arr[z]);
}
return 0;
}
5.1 一个有趣的计算
#include <stdio.h>
#include <math.h>
#include <string.h>
int main()
{
int a[2][3];
a[1/2][0] = 520;
printf("%d",a[0][0]);
return 0;
}
输出:520
解析:1/2 = 0
5.2 函数参数的单向传递
如果参数是普通变量时,那么形参与实参之间的数据传递方式是单向的。
5.3 函数类型与默认的函数类型
函数默认int返回值
aa() {
int df = 22;
return df;
}
aa这个函数是没有提示函数类型的,即函数类型是返回值类型,默认是int;
5.4 复合语句中的变量
完整结构中含有{} 一般就是复合语句是。
范围只在该复合语句中有效。在复合语句外的函数中是不可见的。
5.5 被static 修改的变量
#include <stdio.h>
#include <math.h>
#include <string.h>
int g(int k) {
static int b = 10;
printf("Base int is %d !\n",b);
b += k;
}
int main(void){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
g(arr[0]);
g(arr[1]);
return 0;
}
输出:
Base int is 10 !
Base int is 11 !
解析: 被static 修改的变量,调用完函数后,值被保留下来。
5.6 不生仔的函数
函数是不可以嵌套的,但函数的调用可以。
5.7 main 是带头老大
main函数是不可以被调用的
5.8 使用递归求Fibonacci
#include <stdio.h>
#include <math.h>
#include <string.h>
long Fibonacci(int n) {
if (n == 1 || n == 2) return 1;
return Fibonacci(n-1) + Fibonacci(n-2);
}
int main(void){
printf("%d",Fibonacci(5));
return 0;
}
5.9 迅速了解常用的数据类型
如何定义一个整数类型、浮点类型、字符类型、数组类型的字符串?
答:int a ,double b、char* str, char str[100]="99";
6.0 常量的定义
值初始化后,是不能被修改的。
const int a = 2;
7.0 解析字符为数值类型
atoi("11")
7.1 类型的强转
将double实型/浮点类型的强转为int类型
double a = 2.2;
int b = (int)a;
7.2 数组声明的注意点
在声明时,必须确定数组是怎样的。
可以int arr[][3] = {{1,2,3},{4,5,6}}是可以的,但在声明时就必须初始化。只有这样,才能确定整个数组。
7.3 取余的一个注意点
a%b ,a、b必须是整数
7.4 C语言的整体结构
主函数{子函数(); }组成
C语言主程序必须要有一个主程序 , C语言由"主函数和若干子函数“组成
7.5 关系与逻辑运算的结果
必须是0或1
7.6 存储类别
即函数中的变量,如果不用 static修饰,分配的内存空间是动态的,运行完函数后,会自动被释放,即默认是自动(auto)类型,而不是静态类型(static);
7.7 判断的等价
while(x)语句中的x与 x!=0 或 x==1等价, 即看是否为true
7.8 字符数组式字符串的赋值
在非声明时赋值时, 应使用数组或scanf进行赋值。
7.9 break的使用
只能在循环体内和switch 语句内使用break
8.0 退出多层循环嵌套
goto或return
8.1 8位无符号
能表示的最大十进制数为? 255,十进制0要占着一位。,能表示256种变化
8.2 判断是表达式还是语句
看是否有 返回值,有返回值是表达式,如函数的调用是表达式,语句是没有返回值的。
8.3 八进制的输出
printf("%d=>\156",(int)'n');
156是一个八进制数,会转为十进制再对照ASCII输出。
8.4 几个常见的元字符
\r \t \n对应的是回车、制表、换行符
8.5 指针的基本操作
一个指针p,指向了k=4,那么*p,就是k,你可以给k赋值, *p = 5 相当于k = 5 .你可以获取k的值输出print("%d",*p); 相当于print("%d",k); 那p是什么呢?它就是k的地址,所以你可以将这个地址给其它指针bp,让别的指针也指向k。那么 bp = p = &k;
#include <stdio.h>
#include <math.h>
#include <string.h>
int ab(int a, int b) {
return pow(a,b) + pow(b,a);
}
int main(void){
int a = 10;
int b = 520;
int *p = &a; //让指针p指向a ,执行后 p == &a
*p +=10; //相当于 a += 10
p = &b; //指针重新指向
printf("%d,%d\n",a,*p);
return 0;
}
8.6 借用指针输入
#include <stdio.h>
#include <math.h>
#include <string.h>
int main(void){
int a,*p = &a;
scanf("%d",p); //相当于&a
printf("%d",*p);
return 0;
}
8.7 给形参是指针的参数传参
变量取地址 => 实型
#include <stdio.h>
#include <math.h>
#include <string.h>
//两个值交换
void swap(int *x, int *y) {
int tmp = *x;
*x = *y;
*y = tmp;
}
int main(void){
int a = 10;
int b = 20;
printf("%d,%d\n",a,b);
//传参
swap(&a,&b);
printf("%d,%d",a,b);
return 0;
}
8.8 当指针引用的是数组
#include <stdio.h>
#include <math.h>
#include <string.h>
int main(void){
int arr[3] = {1,2,3};
int *p = arr; //arr直接就是地址了
//下面p+1会找到arr[1]的地址,*(p+1) 就是arr[1]的值了
printf("%p,%d\n",p+1,*(p+1));
return 0;
}
8.9 自己减自己
#include <stdio.h>
#include <math.h>
#include <string.h>
int main(void){
int a[]={0,1,2,3,4,5,6,7,8,9},*p=a,i;
printf("%d==%d","ab"-"ab",p-a);
return 0;
}
输出:0==0
9.0 指针之极度迷惑
int t,*pt=&t;
这相当于
int t;
int *pt;
pt = &t;
这很容易让人以为是
int t;
int *pt;
*pt = &t; #注意*pt 相当被指定的变量。
9.1 论数组的加操作
int arr[5] = {1,2,3,4,5};
错误:arr++;
正确:int a = *(arr+1) #此时 a == 2
9.2 结构体之绝对迷惑
struct student{
char num[10];
int score[2];
};
我们在创建时可以这样:
struct student stu = {"201802",87,80};
9.3 输入值到结构体变量中
struct student{
char num[10];
int score;
};
struct student stu = {"201802"};
scanf("%d",&stu.score);
9.4 连续输入时的char
总结:
有char 字符型用回车的方式隔开
没有char字符型(不包括char arr[100] 这样的)用空格即可。
...
for (int i = 0; i < 2; ++i) {
scanf("%s",&s[i].name);
scanf("%s",&s[i].id);
scanf("%d\n",&s[i].age);
scanf("%c",&s[i].sex);
}
...
在输出字符前,前后的输出后加 \n
9.5 文件的读写操作
首先fopen, 然后再fget、fput、feof, 最后fclose
文本分为文本文件与二进制文件
#include <stdio.h>
#include <math.h>
#include <string.h>
int main(void){
FILE *wf = fopen("C:\\Users\\21192\\Documents\\accatk.sh","w");
char *context = "你好,这是新内容";
fputs(context,wf);
fclose(wf);
FILE *rf = fopen("C:\\Users\\21192\\Documents\\accatk.sh","r");
char txt[200];
printf("%s",fgets(txt,100,rf));
fclose(rf);
return 0;
}
9.6 文本文件输出新方式
#include <stdio.h>
int main ()
{
FILE *fp = fopen("C:\\Users\\21192\\Documents\\accatk.sh","r");
char ch;
while (!feof(fp)) {
ch = fgetc(fp); #获取上次读到的下一个字符
putchar(ch); #输出字符到控制台
}
fclose(fp);
return(0);
}
9.7 强转的作用范围
右旁边数;
float m =1.2,n=1.5;
(int)(m+n)%2/8 //转为:2%2/8 = 0
9.8数组的元素与变量
int arr[3] = {1,2,3};
//输入一个值来改为arr[1]的值。
scanf("%d",&arr[1]);
其中arr[1]就算一个变量a,输入要取地址&a;
9.9 C关键词,有一个既然不是
10.0 函数体的组成
函数体一般包括 变量的定义部分 和 执行部分 。
10.1 只能有三个代表整型常量
八进制 、 十进制 、十六进制
二进制不行!!!
10.2 默认的浮点型
double
10.3 'A'==65?
true
10.4 进制在程序中的表示
const int aa = 0b101; //二进制
int a = 066; //八进制
int b = 0x12d; //十六进制
10.5 浮点型输入,需要注意的问题
#include <stdio.h>
#include <math.h>
int main()
{
float a;
scanf("%2.2f",&a);
printf("%f",a);
return 1;
}
这样的输入,即%A.Bf 格式时,不管输入什么值,都为0,而%2f时,会限制输入。
但输出时,就不会。
10.6 什么是大括号
{}
10.7 print函数的“格式控制”
有两部分“格式说明”、“普通字符”
10.8 if与分号
当if的条件不成立时,不执行下面的第一个“;”的内容。
10.9 switch的限制
switch的变量可以是整数或字符型(其实狭意只能是整数)
(注意不能是字符串类型)
10.10 谁说函数不可以乱声明的
void test(int, int) 是可以通过编译的。
20.1数据结构
20.i 数据结构之扫盲
基本内容
数据 => 数据元素=> 数据项
数据对象 <= 是具有相同性质的数据元素的集合
数据结构 <= 相互之间存在一种或多种特定关系的数据元素的集合。
数据结构包括三方面的内容:逻辑结构、存储结构和数据的运算。
数据结构的形式定义为:数据结构是一个二元组
Data Structure=(D,S)
其中:D是数据元素的有限集,S是D上关系的有限集。
逻辑结构
集合结构:数据元素之间没有任何关系
线性结构:数据元素之间定义了线性关系。1对1。
树形结构:数据元素之间定义了层次关系。1对多。
图形结构:数据元素之间定义了网状关系。多对多。
存储结构
-
顺序存储:数据元素顺序存放,每个存储结点只含一个元素。存储位置反映数据元素间的逻辑关系。存储密度大。但有些操作(如插入、删除)效率较差。
-
链式存储:这种方式不要求存储空间连续,便于动态操作(如插入、删除等),但存储空间开销大(用于指针),另外不能折半查找等。
-
索引存储:除数据元素存储在一组地址连续的内存空间外,还需建立一个索引表,索引表中索引指示存储结点的存储位置(下标)或存储区间端点(下标)。
-
散列存储:通过散列函数和解决冲突的方法,将关键字散列在连续的有限的地址空间内,并将散列函数的值解释成关键字所在元素的存储地址。其特点是存取速度快,只能按关键字随机存取,不能顺序存取,也不能折半存取。
20.1 串——模式匹配
求子串在主串中的位置
20.2 串——存储方式
顺序存储、链式存储
20.3 稀疏矩阵一般的压缩存储方法
三元组顺序表(行, 列, 值) || 十字链表
20.1 稀疏矩阵的压缩存储
aij
1、a00开始时:i++ && j++
2、
i>j(存储下三角), k = i(i-1)/2 + j
i<j(存储上三角), k = j(j-1)/2 +j
3、
以arr[0]开始存储
k = k -1
以arr[1]开始存储
k = k
-
收纳内容:
做这种题,首先要知道不管是对称矩阵(aij == aji),还是三角矩阵,当要说存储上三角或下三角时,计算方法都是一样的,因为他们的同一位置,坐标是相同的 ,或看所给的i,j大小判别。
1、明确计算的是上三角还是下三角,aij,上三角时i<j, 下三角时i>j 。压缩在数组中,计算方法不同,但公式相同,只是变量互换了,当i > j 时,k = i(i-1)/2 + j -1 ,这样算出的是数组为0开始存储的。如果矩阵是以a00开始的,需要i ++, j++ ,再计算。(注意如果题中给a11存储地址为1,那么就是以1开始的,就不要-1了)
设有一个10阶的对称矩阵A,采用压缩存储方式,以行序为主存储,a11为第一元素,其存储地址为1,每个元素占一个地址空间,则a85的地址为( )。
解:由于是对称矩阵,因此压缩存储可以认为只要存储下三角矩阵。
(1,1) 1
(2,1) (2,2) 2
(3,1) (3,2) (3,3) 3
(4,1) (4,2) (4,3) (4,4) 4
(5,1) (5,2) (5,3) (5,4) (5,5) 5
(6,1) (6,2) (6,3) (6,4) (6,5) (6,6) 6
(7,1) (7,2) (7,3) (7,4) (7,5) (7,6) (7,7) 7
(8,1) (8,2) (8,3) (8,4) (8,5) 5
1+2+3+4+5+6+7+5=33
即:对称矩阵按照行序压缩存储位置(i(i-1))/2+j
比如85对应的上一个i与j相同最近是a77,即a77 + 5 = 1+2+3+...+7 + 5
10.7 三元组形式实现稀疏矩阵的转置运算
节省存储空间
10.8 只可能有一个直接前驱,但可有多个直接后继的结构
树
11.0 祖先节点
根到K路径上的任意一个节点是K的祖先节点
11.1 结点的双亲
直接父节点
11.2 分支结点与叶子结点
根据度,度为0是叶子结点(终端结点),否则为分支结点
11.3 兄弟结点
有相同双新的两个结点
11.4 树的深度与高度与层
11.5 有序树与无序树
兄弟节点间是有序的
11.6 路径与路径长度
两个结点之间经过的结点序列是路径,路径长度是序列中的结点数
11.7 数列公式an、Sn
等差:an = a1 + (n-1)d Sn = n*a1 +[dn(n-1)]/2
等比:an = a1q^n-1 Sn = a1(1-q^n)/(1-q)
-
收纳内容
11.7 树的结点数论等价 (记住数列公式即可)
树的结点数 = 所有节点的度+1
11.8 树的度(m)与层(i)最多结点的关系 (记住数列公式即可)
m^(i-1)
比如二叉树 2^(i-1)
11.9 m叉树与树的高度h的作用 (记住数列公式即可)
能计算出树最多有多少节点(m^h - 1)(m-1)
12.0 论最小高度 (记住数列公式即可)
知n个节点的m叉树,最小高度为logm(n(m-1)+1)
12.1 论m叉树的度与结点
-
度为0的结点数+度为1的结点数+..+度为m的结点数 = 这些结点放出的度+1 = 总结点数
-
示例:
比如:
度 个数
0 n个
1 k 个
2 p个
那么 n+k+p = 0*n + 1*k + 2*p + 1 = 节点总数
-
-
对任何非空二叉树T:(根据度n0 = a,n1,n2):n0 = n2+1
-
完全二叉树:结点总数:n0+n2+n1 = (n2+1)+a2 + (1,0) 最多时是1,最少0
12.3 满二叉树
第一层包括最多个数,高度为h的数,总结点是2^h - 1
12.4 完全二叉树
叶子节点只会出现在最后两层,总结点数n为偶数时,最后一层从左到右最后一个没有兄弟节点(双亲只有左节点)。
12.5 二叉排序树
左子树上所有结点的关键字均小于根结点的关键字;右子树上的所有
结点的关键字均大于根结点的关键字;
12.6 平衡二叉树
任一点的左子树与右子树的深度之差绝对值不超过1
12.8 完全二叉树——获取双亲的编号
floor(子编号/2)
12.9 二叉树的链域
在含有_n_个结点的二叉链表中_共有n+1_个空链域
12.10 从前中后遍历的结果能获取的信息
前序遍历:第一个节点是根节点
中序遍历:根节点左边是左子树,右边是右子树
后序遍历:最后一个是根节点
知道前序+中序或 后序+中序即可确定这个二叉树
前序+后序:当是真二叉树时
12.11 树最适合用来表示
元素之间具有分支层次关系的数据
12.12 树转为二叉树
(兄弟线、只保留第一个孩子的线、兄弟线转换45度)
(逆操作:根节点开始右右..关系变为兄弟)
树转换成二叉树的画法:
① 在兄弟结点之间加一条线;
② 对每个结点,只保留它与第一个孩子的连线,抹去与其他孩子的连线;
③ 以树根为轴心,顺时针旋转 。
特点:由树转换成二叉树,其根结点的右子树总是空的!
12.13 森林转为二叉树
(森林中的树转为二叉树后,这些二叉树的根节点连成兄弟线然后旋转)
(逆操作:根节点开始右右右...得到树,然后树变二叉树 )
树转换成二叉树的画法:
① 在兄弟结点之间加一条线;
② 对每个结点,只保留它与第一个孩子的连线,抹去与其他孩子的连线;
③ 以树根为轴心,顺时针旋转 。
设森林F中有三棵树,第一、第二、第三棵树上的结点个数分别为M1,M2,M3,则与森林F对应的二叉树根结点的右子树上的结点个数为M2+M3
12.14 当中序遍历遇到二叉排序树时
会得到一个有序的结果,因为左子树的任一值 < 根结点的值 < 右子树的任一值。
12.15 当向一棵二叉排序树插入一个结点时
这个结点一定会成为二叉排序树的叶子结点
12.16 树节点的删除
-
当删除的是叶子节点时,让该叶子节点的双亲指向该叶子节点的那边指向空,即可。
-
当删除的是度为1的,那么直接将那边替换被删除的节点。
-
如果是度为2的节点,左子树最大的、右子树最小的 替代。那么需要找左节点树中最大的那个结点或右节点树中最小那个结点替换被删除的节点,然后再删除这个前驱或后继节点(它的度为0或1)
还需要看删除的是否是根节点,且需要维护父节点
12.17 树的存储形式
双亲表示法、孩子表示法、孩子兄弟表示法
12.18 树、森林、二叉树 遍历的等价关系
12.19 关于二叉树转为森林的一道树
设森林F对应的二叉树为B,它有m个结点,B的根为P,P的右子树的结点个数为n,森林F 中第一棵树的结点的个数是(m-n)
12.20 哈夫曼二叉树
-
频率转为整数,比如0.1 => 10,比如{3,7,11,8,9,12}要整理为{3,7,8,9,11,12} 将最小两个分别作为左右节点(要求左结点小于右结点),将和作为双亲,双亲的值又放在这个组成中,将3,7删除,重复这样,从而得到哈夫曼二叉树。
-
编号,左线边0,右线1
-
得出“字符”对应频率在图上的路径,比如1010,这样是为了得到路径的长度
-
然后WPL = (频繁100 ) 对应的路径长度 +....
哈夫曼二叉树WPL值最小。
12.21 [图]的组成
顶点(vertex)和边(edge) 图G = (V,E)就是由顶点集V与边集E组成。
顶点集V有穷且非空,边集E可为空。
边是用来表示它们之间的关系的。
- 有向图与无向图:有向图中,边是有方向的,边是顶点的有序对,有序对用<vi, vj>表示,表示从vi到vj的有向边。vi是边的始点,vj是边的终点。
12.22 有向图与有向无环图
有向图:边是有方向的。
有向无环图:首先是有向图,其次从任意顶点出发经过若干条边无法回到该顶点。
12.23 图的出度与入度
出度: 比如一个顶点的出席为x,则有x条边以该顶点为起点
入度:比如一个顶点的出席为x,则有x条边以该顶点为终点
12.24 无向图
无向图的边是无方向的,但一条边类似于一条双向的两条边。可来可往。
12.25 简单图与多重图
简单图:无平行边且无自环图
多重图:有平行边或自环图
12.26 完全图
无向完全图:满足这种图有n(n-1)/2条边, 因为:
有向完全图:边是无向完全图的2倍,即n(n-1)
12.27 稠密图与稀疏图
稠密图 (Dense Graph) :边数接近于或等于完全图
稀疏图 (Sparse Graph):边数远远少于完全图
12.28 有权图
有权图的边可以拥有权值,当然还可以是对象或其它。你可以用它来表示两个地点的飞机票是多少钱,也可以是两个地址的路径长度。
12.29 连通图与连通分量
连通图:任意一个顶点都能直接或间接地互通。
连通分量:如果是连通图,连通分量是自身,如果不是连通图,会有几个极大连通子图(可以是一个)。
12.30 强连通图与强连通分量
有向图时。
12.31 图的的实现方式——邻接矩阵
邻接矩阵:顶点使用一个一维数组,边使用一个二维数组
12.32 图的的实现方式——邻接表
用一个一维数组,只表示它指向谁(只表示我关心的)(有向)或与谁直接连接着,无向图等价于双向。
12.33 图的遍历
深度优先搜索(不撞南墙不回头):类似于树的前序遍历,可以使用栈来实现,一直走,当路断时,往回走,看有分支是没有没走过的。
广度优先搜索(二叉树的层序遍历):类似树的层序遍历,可以使用队列实现,一层层来,且先走的结点的子节点比之后的结点的子节点会更先被遍历 。
12.34连通图的遍历
任意两个顶点都能连通的叫连通图
一次的深度优先搜索即可访问到所有节点,那这个图一定是连通图。
12.35 最小生成树
(Kruskal(克鲁斯卡尔): 线拿出来,按小到大排序,按小到大将边还原,使成环了的边不要。
Prim(普里姆)):任选一个顶点,以这个顶点为一个整体找最近的一边,连上的结点融入这个整体,不断地这样。(任选一个顶点,找最近一边,融入整体)
对一个带权连通无向图,所有生成树中权值之和最小的生成树称为最小生成树。
最小生成树具有如下性质:
(1)最小生成树不一定是唯一的,即最小生成树的树形不唯一
当带权连通无向图中的各边权值互不相等时,这时最小生成树才是唯一的。
(2)最小生成树的边的权值之和总是唯一的。
(3)最小生成树的边数为顶点数减1。
12.36 最短路线——Dijkstra(迪杰斯特拉)算法
一定可以看懂的视频:
https://www.bilibili.com/video/BV1QK411V7V4?spm_id_from=333.337.search-card.all.click
我的理解:
左边是图,右边横边(1,2,3,4,5,6)是顶点,S(T/F)当为T时,说明源顶点到该顶点的距离已是最小,dis是对应的最小距离。即最后会得到从起点顶点到其它顶点的最小距离。
(从一个点发散,遇到更小的更新,最后一个改为T,选小短路径,继续发散)
【开始讲解】 首先开始顶点是1, 1到1为0,好,那么在上面的图1下面的S(T/F)可以改为T(往后看,你会了解);
dis我们就填0; 从1开始,向外发散,1到2是1,好那么2下面S(T/F)可以改为T(因为指向2的就1了,后面没有走向它了,所以2的最小路径已经确定了),dis写1;
而1到3是12, 3下面的S(T/F)不能改为T,因为这不是最后一个指向它的,还有2→3,4→3 。3下面的dik写为12。
明显1→2 < 1→3 ,选最短的2;
从2向外,有2→4, 2→3 , 从2开始2→3 为9,2的dik+9 = 10比之前的12短,所以需要更新3下面的dik为10。然后看2→4 ....
12.37 拓扑排序
(没有人指向我,那我走?)入度从小到大进行消,消时需要将放出的边删掉,再将其余的作为一个整体重新消。注意第消一下就是得到了一个,且排序结果不唯一。
可以用来判断图是否存在环路
12.38 关键路径
(ve: 从左到右,有若干条入度,哪个最长,就选哪个; vi:从右到左谁能把我减最小,选谁, ve-vi 得到关键活动,全部关键活动就是关键路径)
求关键路径,就是在AOE(带权的有向无环图)图中求关键路径,AOE图是一个有向无环图,并且有向图方向始终是从源点走向汇点,且源点汇点都是一个。
视频讲解:
https://www.bilibili.com/video/BV1EQ4y1v7iV?spm_id_from=333.337.search-card.all.click
我的说明:
ve(i):(始点到汇点,两个指向我?我先最大的,继续向汇点走)意为最早发生时间,ve是最长的入度路径,你可以通过看上面视频可理解。这样就得到了ve(i) ,就是从左到右计算最长路径的入度(最好保存其它相等入度,如果有相等的入度,可以方便看缩短哪些可以缩短工程工期,因为两条相等长度路径都缩短,工程才会缩短)。
**vl(i) :(汇点到始点,我指向两个?减后选最小)意为最迟发生时间,比如v3的vl(i)**值就是用最长的v6的8减去v3到v6的最长路径,即8-6=2 。如果是v3,那么看v4-4 =2与 v6-3 = 5,选最小的,就是2,所以v3的vl(i)等于2。v1的vl是不用算的是0;
关键路径:**ve(i) - 下 vl(i) **:
v1 v2 v3 v4 v5 v6
0 1 0 0 1 0
看0(关键活动),0得到v1-v3-v4-v6是关键路径。
12.39 如何根据三元组得出图
(1,2,3), ...
谁指向谁权是多少。
12.40 查找
(1)查找。在数据集合中,寻找满足某种条件的数据元素的过程称为查找。
(2)查找表(查找结构)。用于查找的数据集合称为查找表。
(3)关键字是数据元素中某个数据项的值,用它可以标识一个数据元素,若此关键字可以唯一地标识一个记录,用词关键字为主关键字。
(4)平均查找长度是所有查找过程中进行关键字的比较次数的平均值。
12.41 顺序查找
顺序查找(从一端到另一端),主要用于线性表中进行查找。
其基本思想是从线性表的一端开始,逐个检查关键字是否满足给定的条件。若查找到某个元素的关键字满足给定条件,则查找成功,返回该元素在线性表中的位置。若查找到表的另一端,仍未找到符合给定条件的元素,则返回查找失败的信息。
对线性链表只能进行顺序查找。
时间复杂度是O(n);
平均查找长度是:
ASL成功 = (n+1)/2
ASL不成功 = n+1
12.42折半查找(二分查找)
折半查找法仅适用于顺序存储结构且关键字有序排列。
要查找的是key,首先low指向第一个元素,low指向最后一个, 然后开始折半 mid = (low+high)/2。
进行比较:
key <mid?(height = mid-1):(low = mid + 1)
循环条件是low ≤ high
树的折半查找,时间复杂度是 O(log2(n)) , h = log2(n+1)
12.43 插入排序
方法是从未排序的序列中挑选元素,并将其放入已排序序列的一种。
代码实现:
import com.alibaba.fastjson.JSON;
import com.zjazn.gopoint.common.FileUtil;
import com.zjazn.gopoint.common.JsonGet;
import com.zjazn.gopoint.controller.word.ToPoint;
import java.io.InputStream;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class test {
public static void main(String[] args) {
int a[] = {1,3,2,4,8,7,6,5,22,33,-1};
/**
*插入排序
*/
for (int i = 2; i < a.length; i++) {
if (a[i] >= a[i-1]) {
continue;
}
int tmp = a[i];
for (int j = i-1; j >= 0; j--) {
a[j+1] = a[j];
if (j > 0 && (tmp <= a[j+1] && tmp >= a[j-1]) ) {
a[j] = tmp;
break;
}else if (j == 0) {
a[j] = tmp;
}
}
}
}
}
12.44 希尔排序
计算操作长度的末端j 与i=0,比较,然后i++,j++ 直到端口走到最后一个索引算一趟; 每一趟操作完成j都会向i靠近一位g—
选一定初始长度,一般是一半,从左到右,这个长度减1,再从左到右
int a[] = {5,3,2,55,2};
for (int g = a.length/2; g > 0; g--) { //第一循环就是一趟
for (int i = g,j=0; i < a.length; i++,j++) {
if (a[j] > a[i]) {
int tmp = a[j];
a[j] = a[i];
a[i] = tmp;
}
}
}
return a;
12.** 快速排序
https://www.bilibili.com/video/BV1at411T75o?spm_id_from=333.337.search-card.all.click
快速排序,需要一个中心轴,一般默认都是左边第一个。然后左右两边进行与中心轴比较...
private static void quickSort(int[] arr, int low, int high) {
if (low >= high) {
return;
}
int central = arr[low];
int left = low;
int right = high;
int pos = 1;
while (left < right) {
if (pos == -1) {
//到左边操作
if (arr[left] > central) {
arr[right] = arr[left];
pos = 1;
right--;
} else {
while (arr[left] <= central && left < right) {
left++;
}
}
} else {
//到右边操作
if (arr[right] < central) {
arr[left] = arr[right];
pos = -1;
left++;
} else {
while (arr[right] >= central && left < right) {
right--;
}
}
}
}
arr[left] = central;
//将pivot中心轴(也就是一个元素),放在left=right相等时的这个索引上
//将左右子序列提取出来,单独作为数组,重复上面的操作
quickSort(arr, low, left - 1);
quickSort(arr, right + 1, high);
}
12.45 数据存储方式
顺序存储
链式存储
索引存储
散列存储
12.46 散列存储
38 % 11 = 5;如果位置5已有,请介绍下面的方法解决冲突。
**(1)**开放定址法——线性探测
6,7,8,9,...
(2)开放地址法——二次探测
+1^2, -1^2 , +2^2, -2^2 ...
(3)链地址法
1) 38%11 = 5 => 8, 比较次数4
2) 38%11 = 5 => 4, 比较次数3
12.47 计算字符串的子串
比如字符串长度为n,那么子串长度为1的有n个,2的n-1个,...,即 n + n-1 + n-2 + n-3+...+1 + 1(空串时需要加1)
字串: n(n+1)/2 + 1
非空子串:n(n+1)/2
非空真子串:n(n+1)/2 - 1
12.48 排序算法论是否稳定
稳定排序包括插入排序、冒泡排序、归并排序、基数排序。
非稳定排序算法包括:选择排序、快速排序、希尔排序、堆排序
12.49 归并排序
(刚开始,第一数字一组,然后进行两两合并,保证合并的组是有序的,重复以上操作直到剩下最后一组)
1.将序列中带排序数字分为若干组,每个数字分为一组
2.将若干个组两两合并,保证合并后的组是有序的
3.重复第二步操作直到只剩下一组,排序完成
12.50 基数排序
从右到左依次位放入,从左到右取出。循环,,,
https://www.bilibili.com/video/BV1A54y1D7Kd?spm_id_from=333.337.search-card.all.click
12.51 堆排序
堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次最大值。如此反复执行,就能得到一个有序序列了。
问题:
-
如何由一个无序序列构建成一个堆?
-
如何在输出堆顶元素后,调整剩余元素成为一个新的堆?
12.52 选择排序
选择排序,
第一次,从0~arr.length-1中获取最小位,与0位置交换
第二次,从1~arr.length-1中获取最小位,与1位置交换
第三次,从2~arr.length-1中获取最小位,与2位置交换
...
12.53 广义表
((a,b) ,c,d )
长度:3 (a,b) ,c, d
深度:2 a,b
广义表的表头:(a,b) 表示为((a,b))
广义表的表尾:c,d 表示为(c,d)
存储结构:
12.54 初始数组状态与比较&交换是否有关
1、算法复杂度与初始状态无关的有:选择排序、堆排序、归并排序、基数排序。
2、元素总比较次数与初始状态无关的有:选择排序、基数排序。
3、元素总移动次数与初始状态无关的有:归并排序、基数排序。
12.55 论与存储结构无关
所谓"存储结构无关"应该是指不能获取里面的指定元素,栈不是,队列也不是。
12.56 B树
拿到一个B树看它是几阶(m)的,知道几阶后,就知道了最多有m个分支,每个节点关键字有m/2 ~ m-1个,这很重要,并且关键字之间是有序的。
插入操作:
更多演示:
https://www.cs.usfca.edu/~galles/visualization/BTree.html
12.57 中前后缀表达式
中缀:a+b
转为前缀:+ab
转为后缀:ab+
示例:中缀9+(6-2)*8 则相应的后缀表达式是?答案:962-8*+
=>9(6-2)*8+
=>9(6-2)8*+
==962-8*+
12.58 生成树
如图 1 所示,图 1a) 是一张连通图,图 1b) 是其对应的 2 种生成树。
连通图中的生成树必须满足以下 2 个条件:
-
包含连通图中所有的顶点;
-
任意两顶点之间有且仅有一条通路;
12.59 算法的重要特性
有穷性、确定性、可行性、输入输出
12.60 一个好的算法
正确性、可读性、健壮性、效率与低存储量需求
12.61 算法的 T(n)与O(n)
常见的渐近时间复杂度为
O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O($2{n}$)<O(n!)<O(nn)
12.63 算法的时间复杂度
https://www.cnblogs.com/zjazn/p/15734044.html
12.64 排序的时间复杂度
12.59 关于环形链表的一道题
设环形队列中数组的下标为0~N-1,其队头、队尾指针分别为front和rear(front指向队列中队头元素的前一个位置,rear指向队尾元素的位置),则其元素个数为?
(rear-front +N)%N
12.60 关于线索二叉树的一道题
由线索二叉树的特点,可知一个结点为n的线索二叉树,有n = a0 + a1 + a0-1 得 2a0 + a1 = n + 1。因为a0有两个线索,a1有一个线索,所以对应的线索数(2a0 + a1)为 = n + 1 个。
12.61 关于哈夫曼二叉树的一道题
设一棵哈夫曼树中有1999个结点,该哈夫曼树用于对(____)个字符进行编码。
解:由哈夫曼树二叉树的特点,一棵结点为n的哈夫曼树,可知原有“频率个数”为(n+1)/2, 即 (1999+1)/2 = 1000
12.62 关于无向连通图存储在邻接矩阵
首先无向,那一定是对称矩阵。而是稀疏还是稠密要具体看了。
12.63关于两个有序纯属表的合并,论最小比较次数
两个有序线性表分别有n个元素和m个元素,且n小于等于m,现将其归并为一个有序表,其中最少的比较次数
算法思想:首先,按顺序不断取下两个顺序表表头较小的结点存入新的顺序表中。然后,
看哪个表还有剩余,将剩下的部分添加到新的顺序表后面。
分析:最小的比较次数,是n中最大的都比m最小的小。也就是n全部入新的有序表中,然后再将m有序表一次放入新有序表的外面。即最小比较次数为n;