0%

2017年10月8日 上午11:24

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
create database  xuexiao

go

create table student --先建第一个表
(
sno char(3) primary key,--学号,主键
sname char(8) not null,--学生姓名
ssex char(2) not null,--学生性别
sbirthday datetime ,--学生出生年月
class char(5) --学生所在班级
)

create table student
(
sno char(3) primary key,
sname char(8) not null,
ssex char(2) not null,
sbirthday datetime ,
class char(5)
)

insert into student values (108,'曾华','男','1977-9-1',95033);
insert into student values (105,'匡明','男','1975-10-2',95031);
insert into student values (107,'王丽','女','1976-1-23',95033);
insert into student values (101,'李军','男','1976-2-20',95033);
insert into student values (109,'王芳','女','1975-2-10',95031);
insert into student values (103,'陆君','男','1974-6-3',95031);

select * from student

create table teacher --创建第二个
(
tno char(3) primary key,--教工编号主键
tname char(4) not null ,--教工姓名
tsex char(2) not null ,--教工性别
tbirthday datetime, --教工的出生年月
prof char(6) ,--职称
depart varchar(10) not null--教工所在部门
)


create table teacher
(
tno char(3) primary key,
tname char(4) not null ,
tsex char(2) not null ,
tbirthday datetime,
prof char(6) ,
depart varchar(10) not null
)

insert into teacher values (804,'李成','男','1958-12-2','副教授','计算机系');
insert into teacher values (856,'张旭','男','1969-3-12','讲师','电子工程系');
insert into teacher values (825,'王萍','女','1972-5-5','助教','计算机系');
insert into teacher values (831,'刘冰','女','1977-8-14','助教','电子工程系');


select * from teacher

create table course--第三个表
(
cno char(5) primary key,--课程号,主键
cname varchar(10) not null,--课程名称
tno char(3) foreign key references teacher(tno)--教工编号(外键)
)

create table course
(
cno char(5) primary key,
cname varchar(10) not null,
tno char(3) foreign key references teacher(tno)
)

insert into course values ('3-105','计算机导论','825');
insert into course values ('3-245','操作系统','804');
insert into course values ('6-166','数字电路','856');
insert into course values ('9-888','高等数学','831');

select * from course

create table score
(
sno char(3) foreign key references student(sno) ,--学号(外键)
cno char(5) foreign key references course(cno) not null,--课程号(外键)
degree decimal(4,1) --成绩
)

create table score
(
sno char(3) foreign key references student(sno) ,
cno char(5) foreign key references course(cno) not null,
degree decimal(4,1)
)

insert into score values ('103','3-245',86);
insert into score values ('105','3-245',75);
insert into score values ('109','3-245',68);
insert into score values ('103','3-105',92);
insert into score values ('105','3-105',88);
insert into score values ('109','3-105',76);
insert into score values ('101','3-105',64);
insert into score values ('107','3-105',91);
insert into score values ('108','3-105',78);
insert into score values ('101','6-166',85);
insert into score values ('107','6-166',79);
insert into score values ('108','6-166',81);


select * from score

select * from course

select * from student

select * from teacher

2017年10月7日 下午4:15

参考:

Mac 通过 Homebrew 安装 nginx 并设置开机启动配置 - CSDN博客
mac nginx设为开机启动 - 小唯一的博客 - CSDN博客

了解一下PID(windows中的进程)

参考

mac终端命令简介(适合刚刚入手mac的新人们)二_severus豆_新浪博客

PID的作用:

通过pid来观察一个一个程序是否运行成功(eg:nginx)

我的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Last login: Sat Oct  7 16:03:21 on ttys001
localhost:~ czh$ ps -ef | grep nginx
501 2148 1893 0 4:08下午 ttys001 0:00.00 grep nginx
localhost:~ czh$ ps -ef | grep php
501 516 1 0 3:34下午 ?? 0:00.15 /usr/local/opt/php70/sbin/php-fpm --nodaemonize --fpm-config /usr/local/etc/php/7.0/php-fpm.conf
501 768 516 0 3:34下午 ?? 0:00.00 /usr/local/opt/php70/sbin/php-fpm --nodaemonize --fpm-config /usr/local/etc/php/7.0/php-fpm.conf
501 769 516 0 3:34下午 ?? 0:00.00 /usr/local/opt/php70/sbin/php-fpm --nodaemonize --fpm-config /usr/local/etc/php/7.0/php-fpm.conf
501 2178 1893 0 4:11下午 ttys001 0:00.00 grep php
localhost:~ czh$ ps -ef | grep mysql
501 510 1 0 3:34下午 ?? 0:00.03 /bin/sh /usr/local/opt/mysql/bin/mysqld_safe --datadir=/usr/local/var/mysql
501 744 510 0 3:34下午 ?? 0:00.77 /usr/local/opt/mysql/bin/mysqld --basedir=/usr/local/opt/mysql --datadir=/usr/local/var/mysql --plugin-dir=/usr/local/opt/mysql/lib/plugin --log-error=czhdeMacBook-Pro.local.err --pid-file=czhdeMacBook-Pro.local.pid
501 2182 1893 0 4:12下午 ttys001 0:00.00 grep mysql
localhost:~ czh$ ln -sfv /usr/local/opt/nginx/*.plist ~/Library/LaunchAgents
/Users/czh/Library/LaunchAgents/homebrew.mxcl.nginx.plist -> /usr/local/opt/nginx/homebrew.mxcl.nginx.plist
localhost:~ czh$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist
/usr/local/Cellar/nginx/1.12.1/homebrew.mxcl.nginx.plist: service already loaded
localhost:~ czh$ sudo chown root:wheel /usr/local/Cellar/nginx/1.12.1/bin/nginx
Password:
localhost:~ czh$ sudo chmod u+s /usr/local/Cellar/nginx/1.12.1/bin/nginx

重启mac之后,查看pid

1
2
3
4
5
6
Last login: Sat Oct  7 16:18:05 on console
localhost:~ czh$ ps -ef | grep nginx
0 662 1 0 4:18下午 ?? 0:00.02 nginx: master process /usr/local/opt/nginx/bin/nginx -g daemon off;
-2 685 662 0 4:18下午 ?? 0:00.00 nginx: worker process
501 1103 848 0 4:18下午 ttys000 0:00.00 grep nginx
localhost:~ czh$

2017年10月7日 下午4:15

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
真题仿造

1)p259
基本思路:
这道题的特点是:前负数后正数,无需进行精确排序
我的方法是:i从左向右,指向第一个>=0的数,j从右向左,指向第一个<0的数。直到相遇

void Resort(int R[],int n){
int low = 0;
int high = n-1;

int temp ;

while(low != high){
// 找到下一大于等于0的
while(R[low] < 0 && low < high){
low++;
}
// 找到下一小于0的
while(R[high] >= 0 && low < high){
high--;
}
// low,high位置上元素交换
if(high != low){
temp = R[low];
R[low] = R[high];
R[high] = temp;
}
}
}


2)p259
基本思路:
把题读完,吓我一跳,这只能是0,1,2,3......,n-1
和A一点关系都没有啊

for(i = 0; i < n; i++){
B[i] = i;
}

书上的答案:
总结出来的规律是:关键字值本身即指向了这个关键字在数组中的位置。
void Sort(int A[],int B[], int n){
int i;
for(i = 0; i < n; i++){
B[A[i]] = A[i];
}
}

总结:
这道题出的真恶心!


综合应用题:p263

1)p262
基本思路:
很简单,先统计出c,然后在R[c]中放入

void countSort(int A[],int n,int B[]){
int i ,j;
int c = 0;
for(i = 0; i < n; i++){
c = 0;
// 统计
for(j = 0; j < n ;j++){
if(A[j] < A[i]){
c++;
}
}
// 插入
B[c]= A[i];
}
}



3
p263
基本思路:
以前的冒泡每次从R[0]开始遍历,都是相同的方向。每次遍历都拿出本次最大的数放在最后
这次:每趟遍历都是从左到右,再从右到左。在一趟中要找到一个最大值和一个最小值

void BubbleSort(int R[],int n){
int p = 0;
int q = n-1;
int i;
int temp;

while(p < q){//这里不能写 p != q ,反例是R中元素是偶数
// 右边最大值
i = p;
while(i < q){
if(R[i] > R[i+1]){
temp = R[i];
R[i] = R[i+1];
R[i+1] = temp;
}
i++;
}
q--;

// 左边最小值
i = q;
while(i > p){
if(R[i-1] > R[i]){
temp = R[i];
R[i] = R[i-1];
R[i-1] = temp;
}
i--;
}
p++;
}
}

问题:
1.结束的标志
2.相同值得情况:无所谓,换不换都行。

描述一个最通用情况:p指向左边第一个没有排序的,q指向右边第一个没有排序的,i指向当前指针位置
i = p开始,直到i+1 = q,此时找到一个最大值放在R[q],q--
从i = q开始,直到i-1 = p,此时找到一个最小的值放在R[p],p++
当 p < q是停止


8)p264
基本思路:
我想起我一个数组的插入排序我就写的三次才对,就生气😡!
直接插入排序最基本步骤是:找到位置,插入
这道题中将数组变成了链表,链表适合于插入操作,因此,应该更容易操作

typedef struct LNode
{
int data;
struct LNode *next;
}LNode;


void insertSort(int R[],int n,LNode *&h){
// 这里的h只是一个指针变量,还没有连接上头结点
int i;
LNode *temp;
LNode *pre;

// 初始化头结点
h = (LNode *)malloc(sizeof(LNode));
h->next = NULL;
h->data = 0;


// 循环,每次插入一个R[i]
while(i < n){
// 产生新的节点
temp = (LNode *)malloc(sizeof(LNode));
temp->data = R[i];
temp->next = NULL;

// 找到合适的位置,从后前向后找,知道这个元素小于下一个元素
pre = h->next;
while(pre->next->data < pre->data && pre->next != NULL){
pre = pre->next;
}

// 插入
temp->next = pre->next;
pre->next = temp;

// 下一次循环,下一个值
i++;
}
}

修正版:共两处修改
#include <iostream>
using namespace std;
#define maxSize 100
typedef struct LNode
{
int data;
struct LNode *next;
}LNode;


void insertSort(int R[],int n,LNode *&h){
// 这里的h只是一个指针变量,还没有连接上头结点
int i = 0;
LNode *temp;
LNode *pre;

// 初始化头结点
h = (LNode *)malloc(sizeof(LNode));
h->next = NULL;
h->data = 0;

// 循环,每次插入一个R[i]
while(i < n){
// 产生新的节点
temp = (LNode *)malloc(sizeof(LNode));
temp->data = R[i];
temp->next = NULL;

// 找到合适的位置,从后前向后找,知道这个元素小于下一个元素
pre = h;//修改地方1
while(pre->next != NULL && pre->next->data < R[i]){//修改地方2
pre = pre->next;
}

// 插入
temp->next = pre->next;
pre->next = temp;

// 下一次循环,下一个值
i++;
}
}


int main(int argc, const char * argv[]) {
int n = 7;
int R[7]={3,6,5,1,4,2,7};
LNode *h;
insertSort(R, n, h);

LNode *pre = h->next;
while(pre != NULL){
cout<<pre->data<<endl;
pre = pre->next;
}
return 0;
}




下面是作者的方法:
他的思路是:先把所有的R中的元素全部连接成链表,然后在链表中排序;并且它的选取的数据类型是char

void CreateLink(LNode *&h,char R[],int n){
int i ;
LNode *s,*t;
// 建立头结点
h = (LNode*)malloc(sizeof(LNode));
h->next = NULL;
t = h;
// 把R中的元素放到链表中
for(i = 0; i < n ;i++){
s = (LNode *)malloc(sizeof(LNode));
s->data = R[i];

t->next = s;
t = s;
}
// 做好
t->next = NULL;
}


void Sort(LNode *h){
LNode *p,*p1;
LNode *q,*pre;

if(h->next != NULL){
p = h->next->next;//p指向第二个关键字节点
h->next->next = NULL;//产生只带一个节点的有序表

while(p != NULL){
pre = h;
q = pre->next;

// 在有序表中找到一个节点q,起data值刚好大于p->data
while(q != NULL && q->data < p->data){
pre = q;
q= q->next;
}

p1 = p->next;
p->next = pre->next;
pre->next = p;
p = p1;
}

}
}
总结:
我没看懂他写的 sort()f方法!!!!!





时间复杂度 + 是否稳定

把这些方法汇总一下
每种方法的作用

2017年10月7日 下午4:15

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
插入类排序:
1.直接插入
2.折半插入
3.希尔排序:这里我没有写他的代码,我就是想了解一下,有时间的后面补充

我的错误答案1
void InsertSort(int R[],int n){//又是数组的标准参数模式
int i, j;
int temp;

for(i = 0; i < n; i++){
for(j = i-1; j >= 0; j++){
if(R[i] < R[j]){
temp = R[i];
R[i] = R[j];
R[j] = R[i];
}
}
}


我的错误答案2
void InsertSort(int R[],int n){//又是数组的标准参数模式
int i, j;
int temp;

for(i = 0; i < n; i++){
// 拿上当前这个,把他放到正确的位置,其他的后移
// 有特殊情况
temp = R[i];
for(j = i-1 ; j >= 0;j--){
if(R[j] > temp){
// 后移
R[j+1] = R[j];
}
}

// j+1的位置是空的
R[j+1] = temp;
}
}


我的正确答案:
void InsertSort(int R[],int n){//又是数组的标准参数模式
int i, j;
int temp;

for(i = 0; i < n; i++){
// 拿上当前这个,把他放到正确的位置,其他的后移
// 有特殊情况
temp = R[i];
for(j = i-1 ; j >= 0;j--){
if(R[j] > temp){
// 后移
R[j+1] = R[j];
}
else{
break;//当找到第一个R[j] <= temp 就停止了!!!!!
}
}
// j+1的位置是空的
R[j+1] = temp;
}
}


书上的答案:
void InsertSort(int R[], int n){
int i,j;
int temp;

for(i = 1; i < n;i++){
temp = R[i];
j = i-1;
// 找到第一个刚刚大于前一个数的位置就停止,顺便再把位置移动了。
while(j >= 0 && temp < R[j]){
R[j+1] = R[j];
j--;
}
// 将当前数放到找到的位置上
R[j+1] = temp;
}
}


总结:
刚刚开始第一个排序就狠狠地摔了一跤!!!!
总结其原因发现:是排序的这些题,在生活中太常见了,觉得看一眼就会了。但是,正是这样让我没有仔细的去分析总结他的规律步骤,囫囵吞枣,知道点大概意思就去动手写程序,我不错谁错。
解决方法:“想法转换成步骤”这第一步,就要老老实实的举例子,分析总结他的规律步骤,特别是循环的条件,边界,以及步骤有哪些,这是分析的重点。分析清楚了再去写程序


折半插入(二分查找)
我不理解书上p238的折半最优时间复杂度为 O(nlog2n)
http://www.cnblogs.com/QG-whz/p/5194569.html
这个要涉及到我按算法涉及课上虚的时间复杂度的计算。

基本思路:
折半插入于直接插入的最大改进:他们查找插入位置的方法不同。

#include <iostream>
using namespace std;
#define maxSize 100

void binarySort(int R[],int n,int num){
// 找见位置
int i = 0;//标志左边
int j = n-1;//标志右边
int mid;

while(i <= j){
// 求中间位置,向下取整
mid = (i+j)/2;

// 判断中间值得大小,改变区域的范围
if(R[mid] > num){
j = mid - 1;
}
else{
i = mid + 1;
}
}

// 移动
for(int k = n-1; k>=i ;k--){
R[k+1] = R[k];
}

// 插入
R[i] = num;
}


int main(int argc, const char * argv[]) {
int n = 4;
int R[8]={1,3,5,7};
binarySort(R, n, 7);

int i;
for(i = 0; i < 5;i++){
cout<<R[i]<<endl;
}

return 0;
}
注:我这里只是插入了一个数据

思考这道题的思维过程:
先提出问题:这里的特殊情况有?
1.R中有偶数个元素,和奇数个元素
2.循环继续的条件是 i < j 还是 i <= j?
3.如果我插入的元素R中已存在,是否还是正确的?

上面的这些问题,不是推理出来的,是拿例子试出来的。

你可能会问的几个问题:
为啥 while(i <= j) ? 这里的i <= j是如何确定的
为啥 R[i] = num;?这里为啥插入点是i,j不行吗?
为啥 R[mid] == num 为啥要算到i = mid + 1里?

回答:让我想起一句话“经试验证明......是对的”!!!!!!!!!!!!!!!!!!!

综上所述:
根据上面总结中的这章知识的特点,我觉认为学习这章最重要的是:通过各种不同情况的下的例子,各种特殊情况都要进行测试,最后找到所谓的通式。并且对于循环的条件、边界、步骤要专门的整理。
也就是说:要侧重于“想法转换成步骤”,写程序可以先放放

这章的东西总的来说,粗鲁野蛮,没有逻辑,就是这,你要咋!
这章教会了一个做人的道理:哪有那么多为啥,让你干啥你干啥就行,婆婆妈妈的!😀


交换类排序
1.起泡排序
2.快速排序

void BubbleSort(int R[],int n){
// 一共有n轮,第一轮n个元素,第二轮n-1个元素......每轮都选取本轮中元素最大的值放到最后
int i,j;
int temp;

for(i = 0; i < n;i++){
for(j = 0 ; j < (n-i)-1; j++){//n个元素只需比较n-1次
if(R[j] > R[j+1]){
temp = R[j];
R[j] = R[j+1];
R[j+1] = temp;
}
}
}
}


void BubbleSort(int R[],int n){
// 一共有n轮,第一轮n个元素,第二轮n-1个元素......每轮都选取本轮中元素最大的值放到最后
int i,j;
int temp;
int flag;

for(i = 0; i < n;i++){
flag = 0;
for(j = 0 ; j < (n-i)-1; j++){//n个元素只需比较n-1次
if(R[j] > R[j+1]){
temp = R[j];
R[j] = R[j+1];
R[j+1] = temp;

flag = 1;
}
}
if(flag == 0){//一趟排序如果没有关键字发生交换,则证明序列有序,排序结束
return;
}
}
}

总结:
记住这个flag,来减少一下工作量


快速排序:
基本思想:
输入是给定起始和终止的数组
输出是空
使用递归,每层递归的任务是:找到给定数组中的一个关键字(通常是第一个),在当前序列中保证关键字左边小右边大,最后分发左右在去完成递归

void QuickSort(int R[],int low,int high){//这里的low 和high是下标

if(high > low){
int i = low;
int j = high;
int key;
// 找到关键字
key = R[i];

// 一趟快排
while(j > i){
// j找小的,判j < i ,放到i位置上
while(j >= low && R[j] >= key){//注意点1:这里如果写成R[j] > key 那么就不能处理相同点的情况
j--;

if(j == i){
break;
}
}

R[i] = R[j];

// i找大的,判j < i ,放到j位置上
while(i <= high && R[i] < key){
i++;

if(j == i){
break;
}
}

R[j] = R[i];
}

R[i] = key;
// 递归分发
QuickSort(R, low, i);//注意点2:这里不能写成QuickSort(R, low, i+1);QuickSort(R, i, high); 这样也同样不能处理相同点的情况。书上的哪个具体的例子就可以清楚的反应出来
QuickSort(R, i+1, high);
}
}


书上的答案:
#include <iostream>
using namespace std;
#define maxSize 100

void QuickSort(int R[],int low,int high){//这里的low 和high是下标

if(high > low){
int i = low;
int j = high;
int key;
// 找到关键字
key = R[i];

// 一趟快排
while(j > i){
// j找小的,判j < i ,放到i位置上
while(j > i && R[j] >= key){//注意:这里是>=
j--;
}
if(i < j){
R[i] = R[j];
i++;
}

// i找大的,判j < i ,放到j位置上
while(i < j && R[i] < key){//注意:这里是 <
i++;
}
if(i < j){
R[j] = R[i];
j--;
}
}

R[i] = key;//不要忘了最后将key放入
// 递归分发
QuickSort(R, low, i);
QuickSort(R, i+1, high);
}

}

int main(int argc, const char * argv[]) {
int n = 8;
int R[8]={49,38,65,97,76,13,27,49};
QuickSort(R, 0, 7);

int i;
for(i = 0; i < n;i++){
cout<<R[i]<<endl;
}

return 0;
}
作者的想法是:
如果j在向i方向走时,如果没有找到小于key的点时,那么,就让j停在i的位置上(不能超过i),此时i=j
也就是说,这一路上全是大于key的点。
实现的方法就是: while(j > i && R[j] >= key)中的 j > i

作者比我的好在了:
在整个一趟快排的过程中,始终要求j >= i,其中的j = i就说明了他们相遇了,可以结束这趟快排了。
这就解决了我怕找不到相遇点的情况。
我的解决方法是:每次找到一个点时,我先判断一下此时j==i? 是的话,结束循环就行了。我没要的就是这个点,找到了就行了,其他的不管。


总结:
这道题中的特殊情况有:
1.数组中如果有相同的值,如何处理?
代码注释中:注意点1+注意点2
2.如何能够找到相遇的点?
3.从左边开始,还是右边?


选择类排序
基本思路:
进行n-1次选择,第一次从0~n位置中选最小的,放在0位置上,第二次从1~n位置个中选最小的......



void SelectSort(int R[],int n){
int i,j;
int min;
int temp;

for(i = 0; i < n-1;i++){//n-1次
min = R[i];
// 选出最小的
for(j = i; j < n;j++){
if(min > R[j]){
min = R[j];
}
}
// 放入数组中
R[i] = min;
}
}

如果像上面一样那么数组中就会丢失数据
解决办法是:我们记住最小值的坐标,然后和第一个值R[i]进行交换


void SelectSort(int R[],int n){
int i,j;
int min;
int temp;

for(i = 0; i < n-1;i++){//n-1次
// min每次初始化为第一个值
min = i;
// 选出最小的
for(j = i; j < n;j++){
if(R[min] > R[i]){
min = j;
}
}
// 放入数组中
temp = R[min];
R[min] = R[i];
R[i] = temp;
}
}


堆排序

// 本函数完成在数组R[low]到R[high]的范围内对在位置low上的节点进行调整
void Sift(int R[],int low,int high){
int i = low,j = 2*i;//完全二叉树的性质
int temp = R[i];
while(j <= high){
if(j < high && R[j] < R[j+1]){//若右孩子较大,则把j指向右孩子
j++;
}

if(temp < R[j]){
R[i] = R[j];
i = j;
j = 2*i;
}
else{
break;
}
}
R[i] = temp;
}

void heapSort(int R[],int n){
int i ;
int temp;
// 建立堆
for(i = n/2;i >= 1;i--){
Sift(R,i,n);
}
// 进行n-1次循环,完成堆排序
for(i = n; i >= 2;i--){
// 以下三句换出了根节点中的关键字,将其放入最终的位置
temp = R[1];
R[1] = R[i];
R[i] = temp;

Sift(R,1,i-1);
}
}

总结:
这道题我做不出的原因:
1.我提炼不出关键函数 sift(),这是由于我对题理解的不够透彻
2.我对这个关键函数用程序模拟不出这个过程
1.这的树不是真的让你用指针去链一个树,就是一个一维数组。这是属于数据结构都不知道还做啥。
2.这个过程中有很多不同的情况,我觉的很复杂。

我现在对“把思路转换成步骤”,这一步比较有经验了,主要有以下几点
1.要特意的去考虑特殊情况
2.先找见一个的比较大众,包含性强的情况去解决,然后在加上其他的特殊情况。(这样就条例很多了)


二路归并排序

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);
}
}

merge函数分析:
依照:“思路转换成步骤”
特殊情况有:
1.两边个数不相等,一奇一偶
2.如果两边有相同的数
3.一边的数全大于另一边的数


#include <iostream>
using namespace std;
#define maxSize 100

void merge(int A[],int low,int mid,int high){
if(low != high){
int i = low;//标志
int j = mid+1;//标志
int k;

int B[maxSize];

// 复制一份,因为在A上直接操作可能会丢失数据
for(k = low; k <= high ;k++){
B[k] = A[k];
}

k = low;
while(1){
// 找i,j对应位置数,比大小(相等时,选i),小的数放入k位置上,k++;对应的i++/j++
if(B[i] <= B[j]){
A[k++] = B[i];
i++;
}
else{
A[k++] = B[j];
j++;
}
// 看是否i++/j++之后到了边界,根据不同的情况,看是否继续循环
if(i > mid || j > high){
break;
}
}

while(i <= mid){
A[k++] = B[i++];//这里给忘了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);
}
}

int main(int argc, const char * argv[]) {
int n = 8;
int R[8]={49,38,65,97,76,13,27,49};
mergeSort(R, 0, 7);

int i;
for(i = 0; i < n;i++){
cout<<R[i]<<endl;
}

return 0;
}

2017年10月6日 下午4:15

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
这章的基础能力:将矩阵(二维数组)压缩存储,顺序存储,链式存储都有。
最基本的就是要能通过代码能转换压缩成功。

当做一个题的时候,我们可能会因为想不出第一层的算法,而做不出题。这个是硬伤,没办法。
但是,又有很多时候,我们能表述出来我的的思想,但是就是没法下手,然后思想很有可能就走神了。
出现这种”我知道,但是我就是写不出来“的问题,我总结下来,很多时候都是自己的数据结构没有设计出来。
数据结构(结构体,数组,树,图,众多的变量...)这些其实是一个算法的实现起来的核心,
当数据结构设计出来的时候,也就是说明你的思维依托于代码已近走过一遍了,心中有谱了
因此,数据结构是代码实现的精髓。
当我们需要去记住一个算法的时候,他的数据结构设计一定是我们首先搞定的。


// 5-4 p117
第三层
#define maxSize 100;

void createTrimat(float A[][maxSize],int m,int n){//没有三元数组B[][3]
//扫描有几个元素,建立三元组数组

//再扫描,边扫边给三元组赋值
}


void createTrimat(float A[][maxSize],int m,int n,Trimat B[]){//有三元数组Trimat B[]
}

void createTrimat(float A[][maxSize],int m,int n,float B[][3]){//有三元数组float B[][3]
//这里的B是空的,B[0]也是空的
int i,j;
int k = 1;//这里要从1开始

//扫描,如果不为0,那么添加到B中
for(i = 0 ; i < m;i++){
for(j = 0 ; j < n ;j++){
if(A[i][j] != 0){
B[k][0] = A[i][j];
B[k][1] = i;
B[k][2] = j;

k++;
}
}
}
B[0][0] = k -1;
B[0][1] = m;
B[0][2] = n;
}

总结:
1.以后记住,类似于这种"转换"类型的函数,转换前和转换后变量都作为参数传递
2.这里的类型是float,容易忽视

void print(float B[][3]){
//i,j全部遍历。B中找到输出,找不到输出0

int i,j;
int k = 1;//从1开始

for(i = 0;i < B[0][1];i++){
for(j = 0; j < B[0][2];j++){
if(B[k][1] == i && B[k][2] == j){
cout<<B[k][0]<<" ";
k++;
}
else{
cout<<0;
}
}
cout<<endl;
}
}

void print(float B[][3]){
//i,j全部遍历。B中找到输出,找不到输出0

int i,j;
int k = 1;//从1开始

for(i = 0;i < B[0][1];i++){
for(j = 0; j < B[0][2];j++){
if((int)B[k][1] == i && (int)B[k][2] == j){//这里忘了将float类型强制转换了,jj
cout<<B[k][0]<<" ";
k++;
}
else{
cout<<0;
}
}
cout<<endl;
}
}

// 5-5 p120
十字链表

普通节点结构定义:
typedef struct OLNode
{
int row,col;
struct OLNode *right,*down;
float val;//这里是float型
}OLNode;

头结点结构定义:
typedef struct
{
OLNode *rhead,*chead;
int m,n,k;
}CrossList;
int createCrossListmat(float A[][maxSize],int m,int n , int k ,CrossList &M){
if(M.rhead){
free(M.rhead);
}

if(M.chead){
free(M.chead);
}

M.m = m;
M.n = n;
M.k = k;

// 申请头结点数组空间
if(M.rhead = (OLNode *)malloc(sizeof(OLNode)*m)) return 0 ;

if(M.chead = (OLNode *)malloc(sizeof(OLNode)*n)) return 0 ;

// OLNode r[m];
// OLNode c[n];

// M.rhead = r;
// M.chead = c;

// 头结点数组right down 指针置空
for(int i = 0; i < m;i++){
M.chead[i].right = NULL;
M.chead[i].down = NULL;
}

for(int i = 0; i < n;i++){
M.rhead[i].right = NULL;
M.rhead[i].down = NULL;
}

OLNode *temp_r[maxSize];//建立列链表的赋值指针数组
for(int j = 0 ; j < m;++j){
temp_r[j] = &(M.chead[j]);
}

for(int i = 0;i < m;++i){
OLNode *c = &(M.rhead[i]);
for(int j = 0 ; i < n;j++){
if(A[i][j]!=0){
OLNode *p = (OLNode *)malloc(sizeof(OLNode));
p->row = i;
p->col = j;
p->val = A[i][j];

p->down = NULL;
p->right = NULL;

c->right = p;
c= p;

temp_r[j]->down = p;
temp_r[j]=p;

}
}
}
return 1;
}

总结:
1.涉及链表的操作中,malloc创建节点,初始化节点,连接节点,这三步是永远不会少的
2.关于malloc生产数组,可以直接通过[]来使用,和我们平常的数组使用起来一模一样。
3.-> . 不要混了。操作对象是指针才用->,在这里我就没有想M是个啥,下意识的使用->。以后要专门注意一下函数的参数类型是不是指针
4.最重要的
建立十字链表最核心内容:在遍历的时候,怎么样能让新malloc出来的节点之间正确的连接?
这里的思路是:
先提出问题,能总结出自己的问题也是水平
这时不要想程序怎么写,依然还是在现实中寻找思路
找到思路之后,然后再想怎样用程序实现

这个思路和我以前总结的四层的关系?
当我们在第三层写完注释,开始遍码的时候,就会觉得那里难住了,无法下手
这时我们就需要上面的思路,走出编码,先去寻找思路
也就是说,这个思路是我们遇到问题,解决问题的方法。
而四层,是我们所有题的基本方法,这个思路是我们在进行四层时遇到问题的解决方案


#include<iostream>
using namespace std;


#define maxSize 20

typedef struct OLNode
{
int row,col;
struct OLNode *right,*down;
float val;//这里是float型
}OLNode;

typedef struct
{
OLNode *rhead,*chead;
int m,n,k;
}CrossList;

int createCrossListmat(float A[][4] ,int m ,int n ,int k ,CrossList &M){
if(M.rhead){
free(M.rhead);
}

if(M.chead){
free(M.chead);
}

M.m = m;
M.n = n;
M.k = k;

// 申请头结点数组空间
if(!(M.rhead = (OLNode *)malloc(sizeof(OLNode)*m))) return 0 ;

if(!(M.chead = (OLNode *)malloc(sizeof(OLNode)*n))) return 0 ;

// OLNode r[m];
// OLNode c[n];

// M.rhead = r;
// M.chead = c;

// 头结点数组right down 指针置空
for(int i = 0; i < m;i++){
M.chead[i].right = NULL;
M.chead[i].down = NULL;
}

for(int i = 0; i < n;i++){
M.rhead[i].right = NULL;
M.rhead[i].down = NULL;
}


OLNode *temp_r[maxSize];//建立列链表的赋值指针数组
for(int j = 0 ; j < m;++j){
temp_r[j] = &(M.chead[j]);
}

for(int i = 0;i < m;++i){
OLNode *c = &(M.rhead[i]);
for(int j = 0 ; j < n;j++){
if(A[i][j]!=0){
OLNode *p = (OLNode *)malloc(sizeof(OLNode));
p->row = i;
p->col = j;
p->val = A[i][j];

p->down = NULL;
p->right = NULL;

c->right = p;
c= p;

temp_r[j]->down = p;
temp_r[j]=p;

}
}
}

cout<<M.chead[2].down->val<<endl;
cout<<M.chead[3].down->down->val<<endl;
cout<<M.rhead[1].right->val<<endl;

return 1;
}
int p(){
return 1;
}
int main(){
float A[4][4];
for(int i = 0;i < 4;i++){
for(int j = 0; j < 4; j++){
A[i][j] = 0;
}
}

A[0][3] = 1;
A[1][2] = 3;
A[1][3] = 2;
A[2][0] = 1;
A[3][1] = 2;

CrossList M;
M.rhead = NULL;
M.chead = NULL;//这里要初始化为空,否则在free的报错

createCrossListmat(A,4,4,5,M);

cout<<M.chead[2].down->val<<endl;
cout<<M.chead[3].down->down->val<<endl;
cout<<M.rhead[1].right->val<<endl;

return 0;
}

总结:
这道题中涉及到了结构体指针(结构体内部定义),动态分配数组(malloc),链表添加,二维数组做参数(float A[][4]),结构体的实体做参数(CrossList &M),复杂的结构体、指针操作(输出的时候)
所以说这道题对于巩固基础知识是很有帮助的
malloc 和数组 的区别:数组是在栈中,当运行完一个函数之后,那么在这个函数中生命的数组就被释放(这里我也不太确定),但是事实是,当我们在将题中的malloc换成数组之后,如果在createCrossListmat函数内部输出的话,malloc和数组是一样的,当时当我将输出放在mian之后,就会报错,此时createCrossListmat内部的输出语句依然正常。
结论:在函数中声明一个数组,我们用malloc就行了

判断我们函数的参数是时候实体还是指针?例如这里的CrossList &M
根据我们在函数中怎么用来决定,我们这里的M.***,所以我们传实体本身就行。


// 综合应用题
1)p122
第一层:比较简单,重点让一个指针p一直指向左边第一个空位置就行

#include<iostream>
using namespace std;

#define maxSize 20

void moveElemet(int A[],int n){
int i;
int p = 0;

//什么时候p该加呢?这时想我的“一个方法”。当值向前移动,那么移动到这个值的后面,或者i和p相等的
for(i = 0; i < n;i++){
if(A[i] != 0){
A[p++] = A[i];//一个数组当成两个用
}
}
}


int main(){
int A[10] = {1,2,0,3,0,4,5,6,7};

moveElemet(A, 10);

for(int i =0 ; i < 10;i++){
cout<<A[i]<<endl;//多输出的了
}
return 0;
}

// (2)p122

这里最简单的dp

int findMax(float A[],int n){
if(n == 0){
return 0;
}
else{
result = findMax(A[],n-1);
if(A[n-1]>result) return A[n];
else return A[n];
}
}

int sum(float A[],int n){
if(n == 0){
return 0;
}
else{
return A[n-1] + sum(A,n-1);
}
}

递归:
新的解决问题的范式:新的分解问题的方式,他的可行性验证就是最内部的是否可以简单求解

int average(float A[],int n){//返回值要使用float类型
if(n == 0){
return 0;
}
else{
return (A[n-1] + (n-1)average(A,n-1))/n;
}
}


float average(float A[],int n){
if(n == 0){
return 0;
}
else{
return (A[n-1] + (n-1)*average(A,n-1))/n;
}
}

总结:
求平均值要求函数返回float类型


// (3)p123
i:从左向右 指向第一个偶数
j: 从右向左 指向第一个奇数

循环终止:i < j

void divide(int A[],int n){
int i = 0;
int j = n-1;
int temp ;

while(i < j ){
//找到下一个i
for(int k = i ; k < n;k++){
if(A[k]%2 == 0){
i = k;
break;
}
}
//找到下一个j
for(int k = j ; k >=0;k--){
if(A[k]%2 == 1){
j = k;
break;
}
}
//验证通过,交换值,重新初始化
if(i<j){//这回终于记住在while中要多次判断了
temp = A[i];
A[i] = A[j];
A[j] = temp;

j--;//又忘了在修改之后,重新修改指针
i++;
}
}
}

注意:这道题虽然是两层循环,但是实际上仅对数组进行了一次遍历,此时时间复杂度为o(n)

// (4)p123

i:从左向右 指向第一个大于num的数
j: 从右向左 指向第一个小于num的数
i < j

void divide(int A[],int n){
int i = 0;
int j = n-1;
int temp;
int num = A[n-1];

while(i != j ){
//找到下一个i
for(int k = i ; k < n;k++){
if(A[k] > num){
i = k;
break;
}
}
//找到下一个j
for(int k = j ; k >=0;k--){
if(A[k] < num){
j = k;
break;
}
}
//验证通过,交换值,重新初始化
if(i<j){//这回终于记住在while中要多次判断了
temp = A[i];
A[i] = A[j];
A[j] = temp;

j--;//又忘了在修改之后,重新修改指针
i++;
}
}
}
我这里的问题是:我不会处理A[n-1]如何放在正确的位置上
总结:
解决不了这个问题的按照我的“一个方法”,其实是我的算法有问题,不是简简单单的打点补丁就可以了。也就是说,需要我返回最开始的第一层去完全重新开始
明白,有些问题就是需要我们从头开始,不要怕浪费时间,大家都是这样

void divide(int A[],int n){
int temp ;
int i = 0 ,j = n-1;
temp = A[i];
A[i] = A[j];
A[j] = temp;

temp = A[i];

while(i < j){
//找到右边第一个不满足大于temp的,即小于temp的
while(j > i && A[j] > temp) --j;
//将这个最大的值给了i位置
if(i < j){
A[i] = A[j];
++i;
}
//找左边第一个不满足小于temp的,即大于temp的
while(i < j && A[i] < temp)++i;
//将这个最小的给了j所指向位置
if(i < j){
A[j] = A[i];
--j;
}
}
A[i] = temp;
}

书上的这个算法使用了快排的思想
快排其实代表着一种解决问题的方式
这种解决问题的思维方式才是快排最重要的地方。----------------------------等到了排序那章在总结


// (5)p123
先找见一行中最小的,和最大的;然后再去那这个值去列中对比,如果还是最大、最小,那么则输出


#include<iostream>
using namespace std;


#define maxSize 20
void printMin(int A[][5],int m,int n){
int i,j;
int min;

for(i = 0; i < m;i++){
//假设为每行第一个,然后测试交换
min = A[i][0];
//标志一下最小所在的列,初始化为0,第一个元素
int k[2]={i,0};//在i行,0列
//先着找见一行中最小的
for(j = 0 ; j < n ; j++){
if(min > A[i][j]){
min = A[i][j];
k[0] = i;
k[1] = j;
}
}

int tag = 1;

//去min所在的列进行全部的比较
for(j = 0 ; j < m; j++){//j表示的是行
//若过有一个大于min,那么tag= 0 break;
if(A[j][k[1]] < min){
tag = 0;
break;
}
}

if(tag == 1){
cout<<min<<endl;
cout<<"i = "<<k[0]<<"j = "<<k[1]<<endl;
}
}
}

int main(){
int A[4][5] ={
{6,2,3,4,5},
{1,2,3,4,5},
{11,2,3,4,5},
{13,2,3,4,5}
};
printMin(A,4,5);

return 0;
}

// (7)p123
void revert(int A[][3],int B[][3]){

B[0][0] = A[0][0];
B[0][1] = A[0][2];
B[0][2] = A[0][1];

//遍历,按列号为一组的找
int i ,j;
int k = 0;
for(i = 0;i < A[0][2];i++){
for(j = 1;j <= A[0][0];j++){
if(A[j][2] == i){
B[k][0] = A[j][0];
B[k][1] = A[j][2];
B[k][2] = A[j][1];

//又忘了k++了
k++;
}
}
}
}
总结:
这道题的算法我不知道,直接看的书,书中说出了具体的步骤,自然很好实现
看完之后实现,发现很简单:挑出来元素,然后放在另一个地方
还是比较简单的


// (8)p123
思路的整理:
如何处理一边为0,一边不为0的情况?顺序表的添加麻烦,还得按顺序
先按行,再按列排序。
怎样保证两边的同步?
就和前面一维数组的题一样,我两边都拿上一个,我比较一下,取上的那一边下标移动到下一个,方便下一次操作

和两个一维数组合并思想是一样的

void add(int A[][3],int B[][3],int C[][3]){
int i = 1;
int j = 1;
int k = 1;

//遍历A B直到一个为空
while(i <= A[0][0] && j <= B[0][0]){
//比较 行列是否相同
if(A[i][1] == B[j][1] && A[i][2] == B[j][2]){
//如果位置相同则直接相加

int temp = A[i][0]+B[j][0];
if(temp != 0){//temp有可能为0,不为0的才能添加到C中(这个细节我就忘了)
C[k][0] = A[i][0]+B[j][0];
C[k][1] = A[i][1];
C[k][2] = A[i][2];
k++;
}
i++;
j++;

}
//若果位置不同则加0
else if(A[i][1] > B[j][1]){//谁小取谁
C[k][0] = B[j][0];
C[k][1] = B[j][1];
C[k][2] = B[j][2];

j++;
k++;

}
else if(A[i][1] < B[j][1]){
C[k][0] = A[i][0];
C[k][1] = A[i][1];
C[k][2] = A[i][2];

i++;
k++;

}
else if(A[i][1] == B[j][1] && A[i][2] > B[j][2]){//行相同时
C[k][0] = B[j][0];
C[k][1] = B[j][1];
C[k][2] = B[j][2];

j++;
k++;

}
else if(A[i][1] == B[j][1] && A[i][2] < B[j][2]){//行相同时
C[k][0] = A[i][0];
C[k][1] = A[i][1];
C[k][2] = A[i][2];

i++;
k++;
}
}

//把剩下的一个添加到c

if(i <= A[0][0]){
while(i <= A[0][0])
C[k][0] = A[i][0];
C[k][1] = A[i][1];
C[k][2] = A[i][2];

k++;
i++;
}
}

if(j <= B[0][0]){
while(j <= B[0][0])
C[k][0] = B[j][0];
C[k][1] = B[j][1];
C[k][2] = B[j][2];

k++;
j++;
}
}

C[0][0] = k-1;
C[0][1] = A[0][1];
C[0][2] = A[0][2];

}

总结:
我一开始读完题,错误的以为是要在本身数组上进行操作,那么必定涉及到顺序表位置的大量移动
我这就是自己给自己添加难度,有病
还有,我有一个特殊情况没有考虑进去,就是连个表数据相加可能为0


// (9)p123
思路整理:
这道题和上面三道题是同一类型:关于矩阵的运算+ * 转置
他们都是在原来基本的二维矩阵运算上,存储结构改变为三元组
当然,存储结构一遍,算法基本思想虽让相同,但是值找起来的方式去完全不同了
这也同样说明了存储结构的重要性

重点在于发现找到对应值位置的方式

二维数组的时候,我能直接拿
现在,我只能先找,找到了再拿 多了一步

遍历

总结:
什么样的题简单
稀疏矩阵:以后直接默认使用三元组就行,千万别闲得没事干找十字链表去做,

2017年10月6日 下午4:15

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
真题仿造:p162
求解过程就是仿照p147页的过程:举例,画图,写步骤,抽象总结。

typedef struct BTNode
{
char data;
struct BTNode * lchild;
struct BTNode * rchild;
}BTNode;

BTNode *CreateBT(char pre[],char in[],int l1,int r1,int l2,int r2){
//返回而所构造的二叉树的根节点
BTNode *s;
int i ;
//检查序列是否为空
if(l1 > r1)return NULL;

s = (BTNode *)malloc(sizeof(BTNode));
//这里的循环不是为了找到in[i]这个值,这个值直接拿pre[l1]就行。
//这里的目的是:计数,pre[l1]有多少左孩子 ,这个值用(i-l2)表示
s->lchild = s->rchild = NULL;
for(i = l2;i <= r2;++i){
if(in[i] == pre[l1]){
break;
}
}
s->data = in[i];//s->data = pre[l1];

s->lchild = CreateBT(pre,in,l1+1,l1+i-l2,l2,i-1);
s->rchild = CreateBT(pre,in,l1+i-l2+1,r1,i+1,r2);

return s;
}

总结:
我这这里就没有想到用递归去解决,使用递归这种思考方式,可以省去很多细节上的考虑。
先总结一下这个递归的思路:站在一个节点的角度,去给这个节点赋值,最后返回一下

做事总是一步一步的,缺少整体的宏观的思维,是我想不到递归的本质原因。

递归的难点:在于如何去分割这两个数组
循环的难点:在于如何记录他们之间的关系,保证正确的链接。

为啥只拿先序和中序无法生成树?
只拿前序无法知道左右子树的区别,只拿中序无法知道根节点的位置。


#define maxSize 100;

BTNode *CreateBT(char pre[],char in[],int l1,int r1,int l2,int r2){
if(l1 > r1) return NULL;
BTNode *A[maxSize];
int B[maxSize] = {-1};

//定义根节点
BTNode *root = (BTNode *)malloc(sizeof(BTNode));
root->lchild = root->rchild = NULL;

int i ;
int j ;
int k = 1;

//循环建立节点,并链接

for(i = l1;i <= r1;i++){
//在in中找到当前节点
for(j = l2; j <= r2 ; j++){
if(pre[i] == in[j]){
//生成节点,给节点赋值
BTNode *temp = (BTNode *)malloc(sizeof(BTNode));
temp->data = pre[i];
//当前节点做好记录
A[j] = temp;
B[j] = k;
//从记录找到一个合适的节点,和这个当前节点进行连接
//B中不为-1,且最大的第一个数

for(int q = 0; q < )
}
}
}
//返回跟节点啊
}
总结:我写不下去了,复杂度这都上了三次了,而且中间的步骤太多,而且还麻烦的要死。
这就体现出了,算法选择的重要性,不是能解决问题就行了。而是还要简单方便,容易操作
这里就涉及到了两个数组,三个变量的操作,太复杂了。这就会造成不好控制。


综合应用题p165
2)p165
基本思路:递归遍历就行了,参数使用int &n;

int n = 0;

void computNum(BTNode *bt,int &n){
if(bt != NULL){
computNum(bt->lchild,n++);
computNum(bt->rchild,n++);//我这里不对了,遍历一个节点,但n++两次,肯定不对
}
}

改正版:

我这里要是使用了&n作为参数,就没比要再定义一个int n = 0; 的全局变量了
void computNum(BTNode *bt,int &n){
if(bt != NULL){
n++;
computNum(bt->lchild,n);
computNum(bt->rchild,n);
}
}

(3)p165
基本思路:要在原先递归遍历的基础上,判断一个叶子节点,并计数

void computNum(BTNode *bt,int &n){
if(bt != NULL){

if(bt->lchild == NULL && bt->rchild == NULL){
n++;
}
computNum(bt->lchild,n);
computNum(bt->rchild,n);
}
}

(4)p165
基本思路:想的简单点:链表添加节点需要前驱节点pre和当前节点s, pre->next = s;
so:当前节点和pre连接,然后将当前节点当做pre

void trave(BTNode *bt,BTNode *tail){
if(bt != NULL){
//判断是否是叶子节点

if(bt->lchild == NULL && bt->rchild == NULL){
//连接当前节点
tail->rchild = bt;
//新的前驱节点
tail = bt;
}
trave(bt->lchild,pre);
trave(bt->rchild.pre);

}
}

int main(){
BTNode *head = NULL;
BTNode *tail = (BTNode *)malloc(sizeof(BTNode));
head = tail;
//创建树,这里省略
BTNode *bt;
trave(bt,tail);

return 0;
}


我上面的解法是讲head放在了main中,参数没有head
改进版:

void trave(BTNode *bt,BTNode *tail,BTNode *&head){
if(bt != NULL){
//判断是否是叶子节点
if(bt->lchild == NULL && bt->rchild == NULL){
//如果是head为空,则说明是第一个叶子节点,这时候要对head进行初始化
if(head == NULL){
head = bt;
tail = head;
}
else{
//连接当前节点
tail->rchild = bt;
//新的前驱节点
tail = bt;
}

}
trave(bt->lchild,tail,head);
trave(bt->rchild.tail,head);

}
}
注:这里的链表head是没有头结点的


5)p166
基本思路:链表中的双向链表,需要保存前驱节点

结构体:
typedef struct BTNode
{
int data;
struct BTNode *lchild;
struct BTNode *rchild;
struct BTNode *parent;
}BTNode;


//开始时,pre = NULL,根节点没有父母
void add(BTNode *bt,int BTNode *pre){
if(bt != NULL){
//连接父母
bt->parent = pre;

//当前节点传给左右孩子
add(bt->lchild,bt);
add(bt->rchild,bt);
}
}

//遍历
void trave(BTNode *bt){
if(bt != NULL){
//站在当前节点的角度去思考
//打印出parent节点,直到根节点的null

int i ;
BTNode *temp = bt;
while(temp->parent != NULL){
cout<<temp->data;
temp = temp->parent;
}
cout<<endl;

trave(bt->lchild);
trave(bt->rchild);
}
}


(6)p166
基本思路:这个题就是固定的解法,没啥说的。注意:满二叉树
这道题不是树的遍历,其实是对数组的处理,只不过问题来自于🌲

这里的满二叉树是关键信息:和中序遍历同样都是用来规定一个当前某节点的左右孩子个数。满二叉树左右孩子个数相等


#define n 100
void postArray(int A[],int B[]){
//数组交换位置

//栈中倒叙输出

}

void change(char pre[],int L1,int R1,char post[],int L2,int R2){
if(L1 <= R1){
post[R2] = pre[L1];

//关键在于这里参数的如何确定边界
change(pre,L1 + 1,(L1+1+R1)/2,post,L2,(L2+R2-1)/2);
change(pre,(L1+1+R1)/2+1,R1,post,(L2+R2-1)/2+1,R2-1);
}
}


总结:
第一个错误:数组问题中参数我没有指定左右边界
第二个问题:和p149页后序遍历非递归算法比较,非递归算法的前提是知道BTNode *bt这课树,去求后序遍历数组。
而这道题,前提是前序遍历的数组结果,让求是是后序遍历数组。目的相同但是条件不同

这道题与真题仿造p162页,有相似的地方:都是对数组的处理,难点在于划分每次递归参数的边界。

总结:哥你咋老不去使用递归,让问题复杂!!!

10)p166
t中序中的最后一个节点

TBNode *inLast(TBNode *t){
//直到遇到右指针为有线索的节点为止
// 这个结论是实践总结出来的的
BTNode *p = t;
while(p && !p->rtag){//1代表线索
p = p->rchild;
}
return p;
}

t中序下的前驱
TBNode * inPrior(TBNode *t){
// 若节点t有左线索,则左线索所指节点即为其中序前驱。若无左序线索,则左子树中序最后一个节点即为他的中序前驱
TBNode *p = t->lchild;
if(p && !t->rtag){//t的左树为空和t的左边为线索,这是两种特殊情况,要进行判断
inLast(p);
}

return p;
}

TBNode * treNext(TBNode *t){
TBNode *p;
if(!t->ltag){//有左孩子
p = t->lchild;
}
else if(!t->rtag){//没左孩子,有右孩子
p = t->rchild;
}
else{//没孩子,只有线索
//根据线索,找到第一个有右孩子的节点(线索断了就找见了)
p = t;
while(p && p->rtag){
p = p->rchild;
}
//判断找到的节点是否为空
if(p){
p = p->rchild;
}
}
}

”想法转换成步骤“补充总结:
p178的分析过程体现出了“想法转换成步骤”的一个技巧:找特殊点去分析
有很多特殊情况需要考虑,不能着急慢慢来。

综合总结:
4+1) + 递归 + 想法转换成步骤

思考题
(1)p166
// 基本思路:一次遍历中,遍历左子树和遍历右子树也要输

// void trave(BTNode *bt){
// if(bt != NULL){
// cout<<bt->data<<endl;
// trave(bt->lchild);
// cout<<bt->data<<endl;
// trave(bt->rchild);

// }
// }
上面的错了

int i;
int top = 0;
char pathstack[maxSize];
void allPath(BTNode *p){
if(p != NULL){
pathstack[top] = p->data;

++top;

if(p->lchild == NULL && p->rchild == NULL){
//这里违反了栈的先进后出,是一个栈的使用技巧
for(i = 0;i < top ;i++){
cout<<pathstack[i];
}

allPath(p->lchild);
allPath(p->rchild);

// 这里的top--是很关键的一步,不好理解容易少
// 从我这个节点开始,包括我的左右子树,对于我的上级来说,他是看不见的
// 我的权利很大,完全让我去掌控我的左右子树
// 每当我完成pathstack栈的赋值以后,我都要让栈回复原状,因为我和我的子树们对于其他人在这个问题上是完全没有用的
top--;
}
}
}


总结:
我首先是没有想到栈的逆用,这是一个很关键的问题
其次,对于整个递归的目的没有思考清楚(top--就能反映出来)

2017年10月6日 下午4:15

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253

现在前面的知识忘得厉害的不行。
如何解决:程序你就不用想记了,那玩意今天记明天忘。关键是把题当做数学题的时候,你会作就行了,用我总结的方法,就可以把代码写的八九不离十
邻接矩阵
typedef struct
{
int no;
char info;
}VertexType;

typedef struct
{
int edges[maxSize][maxSize];//邻接矩阵,若果是有权图,则在此句中将int改为float
int n,e;//分别存放顶点数和边数
VertexType vex[maxSize];//存放节点信息
}MGraph;


邻接表
typedef struct ArcNode
{
int adjvex;
struct ArcNode *nextarc;
int info;
}ArcNode;


typedef struct VNode
{
char data;
ArcNode *firstarc;
}VNode;


typedef struct
{
VNode adjlist[maxSize];
int n,e;
}AGraph;




综合应用题
7)p218
基本思路:
这个题思路比较简单,直接将邻接表按行赋值到顺序表中就可以了


void AGraphToMGraph(AGraph *G,MGraph &g){
if(G != NULL){
int i, j;
ArcNode *p;

//给g清空,这一步就给忘了
for(i = 0; i < G->n ;i++){
for(j = 0; j < G->n;j++){
g.edges[i][j] = 0;
}
}

g.n = G->n;
g.e = G->e;

for(i = 0;i < G->n;i++){
p = G->adjlist[i].firstarc;
while(p != NULL){
j = p->adjvex;
g.edges[i][j] = 1;
p = p->nextarc;
}
}
}
}

总结;
我觉得这道题出的有问题,他没有说明是否是代权树!
他给的答案中转移之后并没更改顺序图MGraph中的int n,e;/VertexType vex[maxSize];这两个节点的信息。
如果要是加上这三个值,vex[maxSize]中info 和no是啥我都不知道。



8)p218
基本思路:
简单,就是简单的遍历,连DFS BFS都不是
在邻接表中一共有n层,第k层是不会有k的入度的,我们只需把剩下的层全部遍历一遍,统计出来就行了

int count(AGraph *G,int k){
if(G != NULL){
int i;
ArcNode *p ;
// 统计变量
int count = 0;

for(i = 0; i < G->n;i++){
if(i != k){
p = G->adjlist[i].firstarc;

while(p != NULL){
int j = p->adjvex;
if(j == k){
count++;
break;//加上这句可以少遍历两个
}

p = p->nextarc;
}
}
}

return count;

}
return 0;
}


9)p218
基本思路:
这道题就有点意思了,要用到栈去模拟系统栈,保存路径信息


书上的答案:

void DFS1(AGraph *G,int v){
ArcNode *p;
int stack[maxSize];
int top = -1;

int i, k;
int visit[maxSize];
for(i = 0; i < G->n;i++){
visit[i] = 0;
}

visit[v] = 1;
stack[++top] = v;
while(top != 1){
// 取栈顶元素,先看看,不弹出
k = stack[top];
// 找到这个顶点对应的链表中没有visit过的点
while(p != NULL && visit[p->adjvex] == 1){
p = p->nextarc;
}
// 如果这个顶点对应的链表中所有的点都visit了,则出栈,否则还得留着下回用
if(p == NULL){
top--;
}
else{
visit[p->adjvex] = 1;
stack[++top] = p->adjvex;
}
}
}


我的答案:
void DFS1(AGraph *G,int v){
if(G != NULL){
ArcNode *p;
int stack[maxSize];
int top = -1;

int i, k;
int visit[maxSize];
for(i = 0; i < G->n;i++){
visit[i] = 0;
}

visit[v] = 1;
stack[++top] = v;

while(top != -1){
// 栈中弹出一个节点temp
int temp = stack[top--];
Visit(temp);
// visit[temp] = 1
visit[temp] = 1;
// 将弹出的节点,选出相连的并且没有visit过的节点放入栈中
p = G->adjlist[temp].firstarc;
while(p != NULL){
int j = p->adjvex;
if(visit[j] == 0){//将没有visit的放入栈中
stack[++top] = j;
visit[j] = 1
}
p = p->nextarc;
}

}

}
}

对比总结:
我们的大体方法是一样的:使用栈去保存节点的信息。但是,我们在选取那些节点进行保存的方法上有差别。
我觉得我的思路更好理解:脱离代码,我们从一个起点走,中间要遇见很多的岔路,随便挑一条走。当这条路走到头时,我们需要从当前节点c返回上一个节点a他所连接的另外一个b。也就是说我们要找到b这个节点。
那么,我们最好就是在遍历a的时候,就像b放入栈中,这不就保存了吗!而书的解法保存的是这里的a,而不是b,那么,他就需要从a再找到b这一个过程,在这个过程中a可能还不能从栈中弹出,因为a如果还连着其他节点,下次还会再用。
那么作者保存节点的思路是:站在链表头的角度去看,我从栈中取出来的节点a(没完全去除),找到与他连接的下一个节点b,如果发现没有第二个节点c(没有visit过的节点)与a连接了,那么,a就没有用,我就从stack中彻底拿出

思考题
1218
基本思路:
这个题说的我差点蒙了,你直接说是要建立最小生成树就行了嘛,啰里啰嗦
这也反映了,做题的时候一定要仔细的去读题,读懂题!!!!!!!!
并且,还给出了结构体,只能用克鲁斯卡尔算法


int getRoot(int a,int set[]){
while(a != set[a]) a = set[a];//这里要把a理解成传递的使者

return a;
}


int lowcost(Road road[]){
int i;
int a,b,min;

int set[100];
min = 0;
for(i = 0; i < N;++i){
set[i] = i;
}
// 这里要返回去理解克鲁斯卡尔算法那的“补充总结1:”。
// 说白了,就是在开始之前,我们先要手工在数组中连接一些树,这个不影响后序的操作的
for(i = 0; i < M; ++i){
if(road[i].is == 1){
a = getRoot(road[i].a,set);
b = getRoot(road[i].b,set);

set[a] = b;
}
}

sort(road,M);

for(i= 0; i < M;i++){
a = getRoot(road[i].a,set);
b = getRoot(road[i].b,set);

if(a != b && road[i].is == 0){
set[a] = b;
min +=road[i].cost;
}
}

return min;
}


总结:
让我重新学习了一次克鲁斯卡尔算法,也回去补充了两个知识点:补充总结1:补充总结2

2017年10月6日 下午4:15

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
再次递归总结:
1.将整个问题分成两部分:当前位置元素(1个)+后面所有元素(n-1个)----------整体的思想
2.尾部中解决一个就删除一个-----------简化思想,处理的问题规模越来越小
3.永远解决的都是最后一个相同的问题------------最终的目的

// (6-1) p139
作为程序的编写者,我们要按着程序运行的步骤去想问题

原则:程序是我们表达思想的一种方式。先有思想,然后才有程序。递归依然也要遵从这样的步骤。
容易犯的毛病是:我要套用哪个程序模板,比如说这里的三种遍历树的方式。这种顺序就是反的

拿这个例子来说:
脱离程序,我解决问题的思路是:从左下角开始,我们先站在"+"的位置上,计算"b"+"c"。计算结果之后我将结果保存在"+"所在的位置,然后"b""c"删掉。
第一个问题就解决了。由于每回都删掉最后一个,所以每回的操作其实是一样的。


typedef struct BTNode
{
char data;
struct BTNode *lchild;
struct BTNode *rchild;
}BTNode;

int comp(BTNode *p){

//如果两个子节点都是空,那么直接返回
if(p->lchild == NULL && p->rchild == NULL){//这里写一个就行了,因为这里的都是二元运算,如果加上一元运算,那么这里判断就会变得更加复杂
return p->data - '0';//这里忘了将字符转换成数字了
}
//如果不为空,则取出两个孩子的值,然后和当前节点值p->data相乘
else{
int ans1 = comp(p->lchild) - '0';
int ans2 = comp(p->rchild) - '0';

char c = p->data;

if(c == '*'){
return ans1 * ans2;
}
else if(c == '+'){
return ans1 + ans2;
}
else if(c == '-'){
return ans1 - ans2;
}
else if(c == '/'){
return ans1 / ans2;
}
}
}

写这个函数的时候我提醒自己要站在图中"+"的位置去想如何去操作,
其实我说的就是废话:一个函数,首先要确定的是:输入参数有啥,输出参数最后是啥。
这里的参数是BTNode *p,这个参数就限定了我要处理问题的要站的位置。
输出参数是输入参数位置下所代表的值

那么这个函数的完成内容也就知道了:求当前位置下的最后结果值。
当明确我们的需求的时候写起来也就快的多了。

这个应该加到四步骤之上,所有问题在解决之前,首先要完成的就是需求明确!!!!

总结:
将上面所说的全部连起来的以后,就会发现:
1.如何判断可否使用递归?
1.将整个问题分成两部分:当前位置元素(1个)+后面所有元素(n-1个)----------整体的思想
2.尾部中解决一个就删除一个-----------简化思想,处理的问题规模越来越小
3.永远解决的都是最后一个相同的问题------------最终的目的
2.在能使用递归之后,如何真正的去写这个递归函数具体内容?
1.其实我说的就是废话:一个函数,首先要确定的是:输入参数有啥,输出参数最后是啥。

在这道题中,输入参数是BTNode *p,输出参数是int

// 6-1 p139

int comp(BTNode *p){

if(*p != NULL){
//如果两个子节点都是空,那么直接返回
if(p->lchild == NULL && p->rchild == NULL){//这里写一个就行了,因为这里的都是二元运算,如果加上一元运算,那么这里判断就会变得更加复杂
return p->data - '0';//这里忘了将字符转换成数字了
}
//如果不为空,则取出两个孩子的值,然后和当前节点值p->data相乘
else{
int ans1 = comp(p->lchild) - '0';
int ans2 = comp(p->rchild) - '0';

char c = p->data;

if(c == '*'){
return ans1 * ans2;
}
else if(c == '+'){
return ans1 + ans2;
}
else if(c == '-'){
return ans1 - ans2;
}
else if(c == '/'){
return ans1 / ans2;
}
}
}
else{//这个判断仅仅是为了检查BTNode本身是否是空的,只用第一次,递归的内部是不用的
return 0;
}
}


#include<iostream>
using namespace std;
#define maxSize 20

#define a '1'//这里是用来给char类型赋值的 所以要声明称字符''型,不能直接写数字
#define b '2'
#define c '3'
#define d '4'
#define e '4'

typedef struct BTNode
{
char data;
struct BTNode *lchild;
struct BTNode *rchild;

}BTNode;

int comp(BTNode *p){

if(p != NULL){
//如果两个子节点都是空,那么直接返回
if(p->lchild == NULL && p->rchild == NULL){//这里写一个就行了,因为这里的都是二元运算,如果加上一元运算,那么这里判断就会变得更加复杂
return p->data - '0';//这里忘了将字符转换成数字了
}
//如果不为空,则取出两个孩子的值,然后和当前节点值p->data相乘
else{
int ans1 = comp(p->lchild) ;//这里的返回值是int型
int ans2 = comp(p->rchild) ;

char ch = p->data;

if(ch == '*'){
return ans1 * ans2;
}
else if(ch == '+'){
return ans1 + ans2;
}
else if(ch == '-'){
return ans1 - ans2;
}
else if(ch == '/'){
return ans1 / ans2;
}
}
}
else{//这个判断仅仅是为了检查BTNode本身是否是空的,只用第一次,递归的内部是不用的
return 0;
}
return 0;//走到这里程序就算错了,这只是为了解决编译器出错
}

int main(){

// char c[9] = {'*','-','/','a','+','d','e','b','c'};

BTNode *q = NULL;
//根
BTNode *root = (BTNode *)malloc(sizeof(BTNode));
root->data = '*';
BTNode *p = root;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = '-';
q->lchild = NULL;
q->rchild = NULL;
p->lchild = q;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = '/';
q->lchild = NULL;
q->rchild = NULL;
p->rchild = q;


BTNode *k = p->rchild;
p = p->lchild;//'-'

q = (BTNode *)malloc(sizeof(BTNode));
q->data = a;
q->lchild = NULL;
q->rchild = NULL;
p->lchild = q;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = '+';
q->lchild = NULL;
q->rchild = NULL;
p->rchild = q;

p = p->rchild;//'+'

q = (BTNode *)malloc(sizeof(BTNode));
q->data = b;
q->lchild = NULL;
q->rchild = NULL;
p->lchild = q;


q = (BTNode *)malloc(sizeof(BTNode));
q->data = c;
q->lchild = NULL;
q->rchild = NULL;
p->rchild = q;

//
q = (BTNode *)malloc(sizeof(BTNode));
q->data = d;
q->lchild = NULL;
q->rchild = NULL;
k->lchild = q;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = e;
q->lchild = NULL;
q->rchild = NULL;
k->rchild = q;

cout<<comp(root)<<endl;

return 0;
}


// 6-2 p140

完全按照我上面总结去思考,一点问题都没哟
int getDepth(BTNode *p){
//获取当前的深度位置的深度

if(p != NULL){
//获取后面的所有的深度 左右作比较
int a1 = getDepth(p->lchild);
int a2 = getDepth(P->rchild);
int ans;

if(a1 > a2){
ans = a1 + 1;
}
else{
ans = a2 + 1;
}
}
else{
return 0;
}
}


int getDepth(BTNode *p){
//获取当前的深度位置的深度

if(p != NULL){
//获取后面的所有的深度 左右作比较
int a1 = getDepth(p->lchild);
int a2 = getDepth(p->rchild);

if(a1 > a2){
return a1 + 1;//这里要写成返回

}
else{
return a2 + 1;
}
}
else{
return 0;
}
return 0;
}


// 6-3 p141
就是简单的遍历
题中要求是是任意一个就行,这就更加简单了

void search(BTNode *p,BTNode *&q,int key){//这里要使用的*&
if(p != NULL){
//判断data值
if(p->data == key){
q = p;
return;
}
else{
search(p->lchild,q,key);
search(p->rchild,q,key);
}
}
}

优化:
void search(BTNode *p,BTNode *&q,int key){//这里要使用的*&
if(p != NULL){
//判断data值
if(p->data == key){
q = p;
return;
}
else{
search(p->lchild,q,key);
if(q == NULL){
search(p->rchild,q,key);
}
}
}
}

总结:这里面使用了一个技巧:参数q生命的是*&,说明使用的是函数之外一个一个空间,作用就是可以让每次的递归都是用同一个变量,他们能直接产生影响

BTNode * search(BTNode *p,int key){
if(p != NULL){
//判断data
if(p->data != key){
//找后面的满足条件
BTNode *temp;

temp = search(p->lchild,key) != NULL
if(temp != NULL){
return temp;
}
else{
temp = search(p->rchild,key);
return temp;//这里就不用判断了,直接返回temp就行了,即使他是空的也无所谓。空的说明是这颗树中没有key
}
}
else{
return p;
}
}
else{
return NULL;
}
}


#include<iostream>
using namespace std;
#define maxSize 20

//#define a '1'
//#define b '2'
//#define c '3'
//#define d '4'
//#define e '4'

#define a 1//这里是int类型
#define b 2
#define c 3
#define d 4
#define e 5

typedef struct BTNode
{
int data;//这里是int类型
struct BTNode *lchild;
struct BTNode *rchild;

}BTNode;
BTNode * search(BTNode *p,int key){
if(p != NULL){
//判断data
if(p->data != key){
//找后面的满足条件
BTNode *temp;

temp = search(p->lchild,key);
if(temp != NULL){
return temp;
}
else{
temp = search(p->rchild,key);
return temp;//这里就不用判断了,直接返回temp就行了,即使他是空的也无所谓。空的说明是这颗树中没有key
}
}
else{
return p;
}
}
else{
return NULL;
}
}
int main(){
// char c[9] = {'*','-','/','a','+','d','e','b','c'};

BTNode *q = NULL;
//根
BTNode *root = (BTNode *)malloc(sizeof(BTNode));
root->data = 5;
BTNode *p = root;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = 5;
q->lchild = NULL;
q->rchild = NULL;
p->lchild = q;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = 5;
q->lchild = NULL;
q->rchild = NULL;
p->rchild = q;

BTNode *k = p->rchild;
p = p->lchild;//'-'

q = (BTNode *)malloc(sizeof(BTNode));
q->data = a;
q->lchild = NULL;
q->rchild = NULL;
p->lchild = q;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = 5;
q->lchild = NULL;
q->rchild = NULL;
p->rchild = q;

p = p->rchild;//'+'

q = (BTNode *)malloc(sizeof(BTNode));
q->data = b;
q->lchild = NULL;
q->rchild = NULL;
p->lchild = q;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = c;
q->lchild = NULL;
q->rchild = NULL;
p->rchild = q;
//
q = (BTNode *)malloc(sizeof(BTNode));
q->data = d;
q->lchild = NULL;
q->rchild = NULL;
k->lchild = q;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = e;
q->lchild = NULL;
q->rchild = NULL;
k->rchild = q;

BTNode *ans = search(root,8);

if(ans != NULL){
cout<<ans->data<<endl;
}
else{
cout<<"没有这个值"<<endl;
}
return 0;
}

// 6-4 p142
怎样在递归遍历的时候统计数量?就是计数
第一个节点为1,下一个节点就是1+1 = 2


void trave(BTNode *p,int &i,int k){
if(p != NULL){
if(i == k){
cout<<p->data<<endl;
return;
}
trave(p,i+1,k);
trave(p,i+1,k);
}
}

上面这个无法做到两次连续的trave中i参数的正确性
我的解决方式是:加上返回值 来统计一个在这个递归下走的个数有几个 ,如果已经找到了k,那么则返回-1。得到-1信号之后就不用再找了,直接return 就行了
但是问题是:树中不能有-1


int trave(BTNode *p,int i,int k){
if(p != NULL){
//先判断 层数是否正确 正确则输出并且告诉上面我找到了,你不用找了
if(i == k){
cout<<p->data - '0'<<endl;
return -1;
}

int ans = trave(p,i+1,k);

if(ans == -1){
return -1;
}
else{
ans = trave(p,ans+1,k);

if(ans == -1){
return -1;
}
else{
return ans;
}
}
}
}

上面是我的方案
下面这是书中的方案

int n = 0;
void trave(BTNode *p,int k){
if(p != NULL){
++n;
if(k == n){
cout<<p->data<<endl;
return;
}
trave(p->lchild,k);
trave(p->rchild,k);
}
}

上面是前序遍历
下面是中序遍历+后续遍历

int n = 0;

void trave(BTNode *p ,int k ){
if(p != NULL){
trave(p->lchild,k);

++n;
if(k == n){
cout<<p->data<<endl;
return;
}
trave(p->rchild,k);
}
}

int n = 0;

void trave(BTNode *p ,int k ){
if(p != NULL){
trave(p->lchild,k);
trave(p->rchild,k);

++n;
if(k == n){
cout<<p->data<<endl;
return;
}
}
}


总结:
这里给出了前序,中续,后续遍历
他们都依据一个同一个知识点:p142 图6-9旁边的图
重新理解一些题:就是在基本三种遍历的基础上,增加一个计数的功能,我们首先要确定我们放在那里?然后在解决怎样统计(外部变量)
我写的乱七八糟其实就是没有分析出来正确的方法,这是第一层和第二层的问题
对此,要学会吸收好的方法

总结:
与前几个题比较:这个题的特定是他的问题与二叉树的遍历是互不影响的,也就是说,在完成功能的时候可以先完成遍历,然后在遍历的基础上去添加上这个计数的功能。
而前几个呢?他提出的问题,让解决的问题,都是不能独立在遍历的基础上添加的,这时就需要我们利用完全理解遍历的知识点,然后去完全依据现实的思路去完成功能
最简单的办法是:先试试遍历上加能不能行,不行在就算,能行最好


// 层次遍历

我对层次遍历的思路总结:
1.这里不是用递归!
2.如何解决他们遍历的顺序问题?
1.我们要求的是每一层从左到右
2.在某一层中,当我们从左向右遍历这一层时,每遍历一个我就将这个节点的左右子节点依次放入到队列中,方便下次使用
3.那第一层从哪里来呢?
1.我们手工设置第一层,后面就是自动得了
注意:这点是最重要的难点,最常见的问题就是你不知道怎样开始,然后就卡住不动了。因此:我们有时别太注意一个一个的步骤,就像纠结第一步做啥。这时我们要提高一个高度,从众多的步骤抽离出来,得到一个看似“笼统”方案。

出现这个问题是因为从抽象-->具体的过程中,跳跃性有点强,导致思维的混乱。
一个规律:出现这种情况时,很多都是如果是当做数学题的做,很简单,但是转换成程序的时候,

void level(BTNode *p){
//初始化一个队列 保存BTNode
BTNode *que[maxSize];
int front = 0;
int rear = 0;

que[++rear] = p;

//循环扫描队列中的一个节点,并添加子节点到队列中
while(front != rear){
//输出当前节点
front = (front + 1) % maxSize;
cout<<que[front]<<endl;

//添加子节点

rear = (rear + 1)%maxSize;
que[rear] = p->lchild;

rear = (rear + 1)%maxSize;
que[rear] = p->rchild;
}
}


void level(BTNode *p){
//初始化一个队列 保存BTNode
BTNode *que[maxSize];
int front = 0;
int rear = 0;


if(p != NULL){
que[++rear] = p;

//循环扫描队列中的一个节点,并添加子节点到队列中
while(front != rear){
//输出当前节点
front = (front + 1) % maxSize;
cout<<que[front]<<endl;

//添加子节点
if(q->lchild != NULL){//忘记的了加判断
rear = (rear + 1)%maxSize;
que[rear] = p->lchild;
}

if(q->rchild != NULL){
rear = (rear + 1)%maxSize;
que[rear] = p->rchild;
}
}
}
}

void level(BTNode *p){
//初始化一个队列 保存BTNode
BTNode *que[maxSize];
int front = 0;
int rear = 0;

if(p != NULL){
que[++rear] = p;

//循环扫描队列中的一个节点,并添加子节点到队列中
while(front != rear){
//输出当前节点
front = (front + 1) % maxSize;
cout<<que[front]->data<<endl;

//添加子节点
BTNode *temp = que[front];//这里要指向每次循环中的当前的,不能一直用p
if(temp->lchild != NULL){
rear = (rear + 1) % maxSize;
que[rear] = temp->lchild;
}

if(temp->rchild != NULL){
rear = (rear + 1)%maxSize;
que[rear] = temp->rchild;
}
}
}
}

// 6-5 p145
这道题中作者先给我们提出了两个问题,然后对应的给出了解决的方法。
其中的第一个:如何计算每一层的宽度(节点个数)?
同样,这里是我们数学题很好做,但是转换成程序的时候就有点抓不住关键了。
这时我们从经验中不一步步的思考,直接找关键地方。

typedef struct
{
BTNode *p;
int lno;
}St;


int maxNode(BTNode *b){
//简单的理解就是在层次遍历的基础上加上统计数量,思路就很好很多了
//直接粘过来level中的代码,在这基础上更改

//初始化一个队列 保存BTNode
BTNode *que[maxSize];//这里只要maxSize大于所有的元素个数就行
int front = 0;
int rear = 0;

int level_num[maxSize];//这里只要保证大于层数就行
int i = 0;//层数变量
level_num[i] = 1;//初始化

// 当我们在遍历一层任意元素的时候,这一层一共有多少元素n是我们已经知道了,如果我们还知道如何这上一层元素的位置下标prior
// 那么,我们就可以通过 (front - prior) >= level_num[i] 就知道这层元素遍历完成没有
int prior = -1;//初始是上一层元素最后一个的下标是设置为-1,这是试出来的结果

//当 当前遍历过元素个数 == level_num[i] 时 我们进入下一层

if(p != NULL){
que[++rear] = p;

//循环扫描队列中的一个节点,并添加子节点到队列中
while(front != rear){
//每到一个元素先判断一下当前层数
if((front - prior) >= level_num[i]){
i++;
}

//输出当前节点
front = (front + 1) % maxSize;
cout<<que[front]->data<<endl;

//添加子节点
BTNode *temp = que[front];//这里要指向每次循环中的当前的,不能一直用p
if(temp->lchild != NULL){
rear = (rear + 1) % maxSize;
que[rear] = temp->lchild;

//加下面一层
level_num[i+1]++;

}

if(temp->rchild != NULL){
rear = (rear + 1) % maxSize;
que[rear] = temp->rchild;

//加下面一层
level_num[i+1]++;
}
}
}
}



#include<iostream>
using namespace std;
#define maxSize 20

//#define a '1'
//#define b '2'
//#define c '3'
//#define d '4'
//#define e '4'

#define a 1
#define b 2
#define c 3
#define d 4
#define e 5

typedef struct BTNode
{
int data;
struct BTNode *lchild;
struct BTNode *rchild;

}BTNode;

void maxNode(BTNode *p,int level_num[]){
//简单的理解就是在层次遍历的基础上加上统计数量,思路就很好很多了
//直接粘过来level中的代码,在这基础上更改


//初始化一个队列 保存BTNode
BTNode *que[maxSize];//这里只要maxSize大于所有的元素个数就行
int front = 0;
int rear = 0;

// int level_num[maxSize];//这里只要保证大于层数就行
int i = 0;//层数变量
level_num[i] = 1;//初始化

// 当我们在遍历一层任意元素的时候,这一层一共有多少元素n是我们已经知道了,如果我们还知道如何这上一层元素的位置下标prior
// 那么,我们就可以通过 (front - prior) >= level_num[i] 就知道这层元素遍历完成没有
int prior = 0;//初始是上一层元素最后一个的下标是设置为0,这是试出来的结果

//当 当前遍历过元素个数 == level_num[i] 时 我们进入下一层
if(p != NULL){
que[++rear] = p;

//循环扫描队列中的一个节点,并添加子节点到队列中
while(front != rear){

//输出当前节点
front = (front + 1) % maxSize;
// cout<<que[front]->data<<endl;

//添加子节点
BTNode *temp = que[front];//这里要指向每次循环中的当前的,不能一直用p
if(temp->lchild != NULL){
rear = (rear + 1) % maxSize;
que[rear] = temp->lchild;

//加下面一层
level_num[i+1]++;
}

if(temp->rchild != NULL){
rear = (rear + 1) % maxSize;
que[rear] = temp->rchild;

//加下面一层
level_num[i+1]++;
}

//每到一个元素先判断一下当前层数
//当处理完一个元素之后,判断他是不是这层中最后一个,是的话进入下一层
if((front - prior) == level_num[i]){
prior = prior + level_num[i];
i++;
}
}
}

}
int main(){
// char c[9] = {'*','-','/','a','+','d','e','b','c'};

BTNode *q = NULL;
//根
BTNode *root = (BTNode *)malloc(sizeof(BTNode));
root->data = 6;
BTNode *p = root;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = 7;
q->lchild = NULL;
q->rchild = NULL;
p->lchild = q;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = 8;
q->lchild = NULL;
q->rchild = NULL;
p->rchild = q;

BTNode *k = p->rchild;
p = p->lchild;//'-'

q = (BTNode *)malloc(sizeof(BTNode));
q->data = a;
q->lchild = NULL;
q->rchild = NULL;
p->lchild = q;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = 9;
q->lchild = NULL;
q->rchild = NULL;
p->rchild = q;

p = p->rchild;//'+'

q = (BTNode *)malloc(sizeof(BTNode));
q->data = b;
q->lchild = NULL;
q->rchild = NULL;
p->lchild = q;


q = (BTNode *)malloc(sizeof(BTNode));
q->data = c;
q->lchild = NULL;
q->rchild = NULL;
p->rchild = q;

//
q = (BTNode *)malloc(sizeof(BTNode));
q->data = d;
q->lchild = NULL;
q->rchild = NULL;
k->lchild = q;

q = (BTNode *)malloc(sizeof(BTNode));
q->data = e;
q->lchild = NULL;
q->rchild = NULL;
k->rchild = q;


int level_num[maxSize]={0};
maxNode(root,level_num);

for(int i = 0; i < 12; i++){
cout<<level_num[i]<<endl;
}
return 0;
}

总结:
书中的方法和我的不一样:把挑选最大值的功能放到了程序里面,而我的是在函数外面对数组同一遍历一次

// 二叉树遍历算法的改进
1.非递归


void preorderNonrecursion(BTNode *bt){
BTNode *stack[maxSize];
int top = -1;
BTNode *p = bt;

while(top != -1){
stack[++top] = p;

if(p->lchild == NULL){

if(p->rchild == NULL){
p = stack[top--];
}
}
}
}

void preorderNonrecursion(BTNode *bt){
if(bt != NULL){
BTNode *stack[maxSize];
int top = -1;
BTNode *p;

stack[++top] = bt;

while(top != -1){
p = stack[top--];
visit(p);
if(p->rchild != NULL){
stack[++top] = p->rchild;
}

if(p->lchild != NULL){
stack[++top] = p->lchild;
}
}
}
}

void inorderNonrecursion(BTNode *bt){
if(bt != NULL){
BTNode *stack[maxSize];
int top = -1;
BTNode *p;
p = bt;

while(top != -1 || p != NULL){
//左孩子存在,则做孩子入栈
while( p != NULL){
stack[++top] = p;
p = p->lchild;
}
//在栈不为空的情况下出栈,并输出出栈节点
if(top != -1){
p = stack[top--];
visit(p);
p = p->lchild;
}
}
}
}



void postorderNonrecursion(BTNode *bt){
if(bt != NULL){
//定义两个栈
BTNode *stack1[maxSize];
int top1 = -1;
BTNode *stack2[maxSize];
int top2 = -1;

BTNode *p = NULL;
stack[++top1] = bt;

while(top1 != -1){
p = stack1[top1--];
stack2[top2] = p;

if(p->lchild != NULL){
stack1[++top1] = p->lchild;
}

if(p->rchild != NULL){
stack1[++top1] = p->rchild;
}

}

while(top2 != -1){
//出栈序列即为后续遍历序列
p = stack2[top2--];
visit(p);
}
}
}

总结:
这三种的联系是:比作走路,看你到了分叉口怎样走,以及走了之后如何保存。
我即使能试出来书中p147页的图,我也总结不出p148步骤!!
这就是我为啥写不出来的原因:从现象中总结不出步骤,这是第二层的事。



// 线索二叉树
// 线索二叉树的节点定义
typedef struct TBTNode
{
char data;
int ltag,rtag;
struct TBTNode *lchild;
struct TBTNode *rchild;
}TBTNode;

中序线索化

void InThread(TBTNode *p,TBTNode *&pre){
// 这里的&,就是递归前后相互影响的一种技巧

//其实就是在中序递归遍历的基础上一个步骤就行
if(p != NULL){
//左边的节点前驱不是当前节点,因为这里是中序遍历,中序遍历的当前节点‘根’不是‘左边节点’的前驱
InThread(p->lchild,pre);
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}

if(pre != NULL && pre->rchild == NULL){
pre->rchild = p;
pre->rtag = 1;
}

pre = p;
// 这里的pre是指的当前的节点p,因为这里是中序遍历,中序遍历的‘根’是‘右边节点’的前驱
InThread(p=p->rchild,pre);
}
}

总结:
过程是这样理解:我是当前的根,我把我拿到的前驱pre让自己的左节点去用,而我自己用这个链接自己的右边节点,我自己用完之后,我把我自己作为前驱pre,传给自己的右节点,让他去用

主程序
void createInThread(TBTNode *root){
TBTNode *pre = NULL;

if(root != NULL){
InThread(root,pre);
pre->rchild = NULL;
pre->rtag = 1;
}
}


遍历中序线索二叉树
//中序序列下第一个节点的算法
TBTNode *First(TBTNode *p){
while(p->ltag == 0){
p = p->lchild;
}
return p;
}

//节点p在中序下的后续节点的算法
TBTNode *Next(TBTNode *p){
if(p->rtag == 0){
return First(p->rchild);
}
else{
return p->rchild;
}
}

void Inorder(TBTNode *root){
for(TBTNode *p = First(root);p != NULL;p=Next()){
visit(p);
}
}

总结:
我第一次看这个程序我也看不懂,这样写的原因是啥?
然后我自己画了一个线索二叉树的图,在线索也加上之后,我就开始先认为的将这个线索二叉树的遍历过程遍历出来,然后再将这个过程变成程序代码。
发现一个问题:让我在图上求解这道题我可以,但是让我总结规律,去写程序,这个规律我总结不出来,导致我写不出程序。
书上人的基本思想是:就像一个链表一个去访问这个线索二叉树,链表中我们都是通过next去找下一个,这里作者就封装了一个next方法,这样就更像一个链表操作了。


假如让你发明一个算法,该如何发明呢?就像线索二叉树一样。
首先你要提出一个设想,我希望改进成什么样子,eg:线索二叉树中希望将二叉树更改成类似链表一样,直接就能够找到前驱和后继节点。
然后,我们去尝试,这里的尝试可以吸取其他算法的经验,去达到我们想要的目标。eg:线索二叉树中就首先更改了节点的内部结构。
然后最重要的一步是:用你改进之后的结构,去尝试解决问题,判断是否可以走通。
最难的一步:将能走通的结构和想法,将这种想法汇总总结成步骤,最后写成程序

前序线索二叉树

线索化
思路概述:
这种题现在就不能直接得出思路了,需要在纸上先观察一下。

void preThread(TBTNode *p,TBTNode *&pre){
if(p != NULL){
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
// 将这里的rchild理解成next,就和链表一样了,好理解了
if(pre != NULL && pre->rchild == NULL){
pre->rchild = p;
pre->rtag = 1;
}
//注意,这里的递归入口处有条件限制,左右指针不会线索才继续递归
//中序就不用
if(p->rtag == 0){//上面可以看到,可会对p->lchild 在执行这步之前就会被赋值为线索,因此需要判断一下
preThread(p->lchild,pre);
}

if(p->rtag == 0){
preThread(p->rchild,pre);
}
}
}

void preorder(TBTNode *root){
TBTNode *p = root;
while(p != NULL){
while(p->ltag == 0){
visit(p);
p=p->lchild;
}

visit(p);
p = p->rchild;
}
}

总结:
如何去解决我上面提出“解决将能够走通的想法,转换成代码实现很困难(尤其在树中)”
在书中p146-149就给出了很好的示范!!
在整理思路的时候,他举一个具体的例子,并且画了图。在这基础上他还1.2.3...具体的描述了在图中分析问题思路的步骤。
这里的问题是:这是一个具体的例子,而我们需要的是一个通用的程序。
这是我们就需去将1.2.3...这样的步骤抽象:将1.2.3..中的每一步当做一个单位,去‘对比’出一个单位分为哪些步骤,这些单位有哪些异同,结果合并和一个交集。记住:我们要的是一个通式!!


后序线索二叉树
void postThread(TBTNode *p,TBTNode *&pre){
if(p != NULL){
//左子树要拿上当前前驱pre
postThread(p->lchild,pre);
//右子树要拿上左子树最后一个前驱pre
postThread(p->rchild,pre);

//我(当前p)要拿上右子树最后一个前驱pre

if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}

if(pre != NULL && pre->rchild != NULL){
pre->rchild = p;
pre->rtag = 1;
}

pre = p;
}
}

通俗语言理解线索化的过程:
我现在有的我自己和我的前驱,我不管前驱是从哪来的,给我我就认为是对的
我把我的前驱给了我左边和右边的子树,然后他们自己就会连接好线索,我同样不管他们是怎样操作
最后,我把我自己和前驱链接上,然后再把前驱指向自己。这就完成了

注意:在这个我的左右字数链接线索的完成之后,前驱pre和一开始给我的不一样了。!!!

两个不管:
前驱从哪来的不管
左右子树怎样操作的我不管

线索化最简单的理解是:就是在三种递归的基础模板上(p142),多了一步:类链表的前后链接(pre->rchile 类似于pre->next ;p->lchhild 类似于 p->prior)
写程序的时候必须这样想,要不你写不出递归程序!!!!
理解到这里,这个程序就很好谢了。


总结:
1-5章,他的操作题就两种顺序表和链表这两大类,求解的问题一般都是空间移动,统计,增删改查啥的,难题集中在算法你不知道,你设计不出来一套合适的算法
而在第六章,求解的问题很多都设计到记录的保存,和递归,他的题的共性是:给你当做数学题做太简单,当成程序题太难。原因就是前面说的转换能力不行。
好在我已经总结写出了解决的方法。