Alex Chiu


  • Home

  • About

  • Categories

  • Archives

  • Schedule

  • Sitemap

CSAPP-malloc lab

Posted on 2018-12-14 | In CSAPP

实现思路

所用策略:Segregated lists + best fits + explicit free list

upload sucssful

堆的结构:
地址从上到下是从低到高
upload successful

upload ful

分离适配的原理:

allocator 维护一个空闲链表的数组,每个空闲链表是和一个大小类相关联的,被组织成某种类型的显式或者隐式链表,每个链表包含潜在的大小不同的块。

为了分配,要先确定请求的大小的类,并且对适当的空闲链表做首次适配,如果找到,就分割,并把剩余部分查到空闲链表。如果找不到,就搜索下一个更大的类,如果还是没有,就向操作系统请求额外的堆内存,然后从这个堆内存中分配出一个块,并把剩余部分放在适当的大小类中。

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
/* single word (4) or double word (8) alignment */
#define ALIGNMENT 8

/* rounds up to the nearest multiple of ALIGNMENT */
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)


#define WSIZE 4 //word and header/footer size
#define DSIZE 8 //double word size (bytes)

#define INITCHUNKSIZE (1<<6) //the size of extended heap
#define CHUNKSIZE (1<<12) //extend heap

#define MAX(x,y) ((x)>(y))?(x):(y)
#define MIN(x,y) ((x)<(y))?(x):(y)

#define PACK(size,alloc) ((size)|(alloc))

//read and write a word at addr p
#define GET(p) (*(unsigned int*)(p))
#define PUT(p,val) (*(unsigned int*)(p) = (val))

#define SET_PTR(p,ptr) (*(unsigned int*)(p) = (unsigned int)(ptr))

#define GET_SIZE(p) (GET(p)&~0x7)
#define GET_ALLOC(p) (GET(p)&0x1)

//given block ptr bp,compute address of its header and footer
#define HDRP(bp) ((char*)(bp)-WSIZE)
// (char*bp)+size -wsize= next's block's bp 's position,-DSIZE,that means
// previous block's foot address
#define FTRP(bp) ((char*)(bp)+GET_SIZE(HDRP(bp))-DSIZE)

#define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE((char *)(bp)-WSIZE))
#define PREV_BLKP(bp) ((char *)(bp) - GET_SIZE((char *)(bp)-DSIZE))

#define PRED_PTR(ptr) ((char*)(ptr))
#define SUCC_PTR(ptr) ((char*)(ptr)+WSIZE)

#define PRED(ptr) (*(char **)(ptr))
#define SUCC(ptr) (*(char **)(SUCC_PTR(ptr)))

#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))
#define LIST_SIZE 16

全局函数

1
2
3
4
5
static void *extend_heap(size_t words);
static void *coalesce(void *bp);
static void *place(void *bp,size_t asize);
static void insert(void *bp,size_t size);
static void delete(void *bp);

初始化列表

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
/* 
* mm_init - initialize the malloc package.
*/
int mm_init(void)
{
void *heap;
if((heap=mem_sbrk(4*WSIZE))==(void*)-1)
return -1;

int idx;

//initialize the list
for(idx=0;idx<LIST_SIZE;idx++){
list[idx] = NULL;
}

//alignment padding
//按照上图堆的结构来初始化
PUT(heap,0);
PUT(heap+(1*WSIZE),PACK(DSIZE,1)); //prologue header
PUT(heap+(2*WSIZE),PACK(DSIZE,1)); //prologue footer
PUT(heap+(3*WSIZE),PACK(0,1)); //Epilogue header

if(extend_heap(INITCHUNKSIZE)==NULL)
return -1;
return 0;
}
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


/*
* mm_malloc - Allocate a block by incrementing the brk pointer.
* Always allocate a block whose size is a multiple of the alignment.
*/

void *mm_malloc(size_t size)
{
//ignore spurious requeses
if(size==0)
return NULL;

if(size<=DSIZE)
size = 2*DSIZE;
else
size = ALIGN(size+DSIZE);

int idx = 0;
size_t ssize = size;
void *ptr = NULL;

while(idx<LIST_SIZE){
//链表类的大小分别是 1 1 2 4 8 16 32...
// 分别存储 {0},{1},{2,3},{4,5,6,7}..
// 通过循环 ssize>>=1 与idx,找到该分配的
//字节大小所在的链表
//然后再内嵌一个循环,在这个链表中寻找大小适 //合的pointer
if((ssize<=1)&&(list[idx]!=NULL)){
ptr = list[idx];
while(ptr&&((size>GET_SIZE(HDRP(ptr))))){
ptr = PRED(ptr);
}
//如果找到了就break
if(ptr!=NULL)
break;
}
ssize>>=1;
idx++;
}

//如果找不到,向系统申请额外的堆内存
if(ptr==NULL){
int extendsize = MAX(size,CHUNKSIZE);
if((ptr=extend_heap(extendsize))==NULL)
return NULL;
}

//在free块中allocate size大小的块
ptr = place(ptr,size);

return ptr;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
void mm_free(void *ptr)
{
size_t size = GET_SIZE(HDRP(ptr));

//把该块头脚末位都标注为0,说明这一块是free
PUT(HDRP(ptr), PACK(size, 0));
PUT(FTRP(ptr), PACK(size, 0));

/* 插入分离空闲链表 */
insert_node(ptr, size);
/* 合并 */
coalesce(ptr);
}

并发与并行区别

Posted on 2018-12-13

https://www.cnblogs.com/liqiuhao/p/8082246.html

逻辑控制流

程序加载到内存中执行,(这时变成进程),操作系统会通过虚拟内存机制,通过让它和其他进程分时段占用CPU,使其产生独占CPU的假象。在CPU执行一个进程的指令的时候,被执行的许多指令连接起来,就构成了逻辑控制流。

并发

就是两个逻辑控制流交替进行

upload succe

A与B,A与C并发

并发与CPU个数或者计算机个数无关

并行

如果两个逻辑控制流同时(一个cpu时段内)在不同的cpu(多核)或者计算机上被执行,我们就称其为并行。

filename already existsmed

CSAPP-SHELL LAB

Posted on 2018-12-12

整个过程中要考虑显式阻塞

1.在访问全局变量(jobs)以及调用给定函数的时候,要阻塞所有的信号,务必保证这些使用for循环遍历的函数不被中断。

2.在一些函数或者指令有必须的先后顺序的时候,要阻塞,保证前一个函数调用完成,再调用后面一个函数。

其他事项:

filename already exists, renaed

1
2
//判断是否是当前引起停止信号的是否是前台进程
volatile sig_atomic_t fg_stop_or_exit;

eval函数

功能是对用户输入的参数进行解析,命令有两种,一种是内置的命令,会立刻执行,否则就要fork一个新的子进程并且把该任务在子进程的上下文中运行。如果是前台任务则需要等到它运行结束才返回

每个子进程必须有一个独一无二的进程组id,通过在fork()之后子进程的Setpgid(0,0)实现,这样当我们向前台程序发送ctrl+c或者ctrl+z命令才不会影响到后台程序。否则所有的子进程会与当前的tsh shell进程为同一个进程组,发送信号的时候,前后台子进程都会收到。

同时fork新进程的前后要阻塞SIGCHLD信号,防止出现竞争的同步错误:fork之后会在job列表里添加job,信号处理函数sigchld_handler回收进程后会在job列表中删除,如果信号来得很早,那么就可能发生先删除后添加的情况,那么job就会永远在列表中(内存泄漏?),所以我们先block掉SIGCHLD,添加job后再还原。

说白了就是要避免僵尸进程,防止父进程没有给子进程收尸,屏蔽这个信号,那么父亲进程就会不关心这个子进程,子进程结束将由init进程去处理。

setpgid 函数
1
int setpgid (pid_t pid,pgid_t pgid);

该函数的意义是找到进程ID为pid的进程,将其进程组ID修改为pgid,如果pid=0,说明要修改进程组ID。如果是

1
setpgid(0,0)

表示创立新的进程组,并且指定的进程会成为进程组的首进程。

如果执行成功就返回组识别码,如果有错误则返回-1,错误原因保存在errno中。

具体实现

eval函数实现如下:

if builtin_command return 0,then shell starts a new child process,and execute the requested programs in the child process,if the user asks for running the program in background, then shell return back to the top of the loop,waiting for next command. otherwise shell uses the waitpid function to wait for the jobs ‘ termination. when jobs terminates,shell begin a new loop.

参考:
https://blog.csdn.net/zxygww/article/details/25976107

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
void eval(char *cmdline)
{
char *argv[MAXARGS]; //argument list execve()
char buf[MAXLINE];
int argc;
int bg; //whether the job is in fg or bg
bg = parseline(cmdline,argv);
sigset_t mask_chld,mask_all,mask_prev;
pid_t pid;

sigemptyset(&mask_chld);
#把SIGCHLD信号赋给mask_chld
sigaddset(&mask_chld,SIGCHLD);
#fill所有SIG信号给mask_all
sigfillset(&mask_all);
strcpy(buf,cmdline);

//empty command
if(argv[0]==NULL){
return ;
}
else if(!builtin_cmd(argv)){
//如果不是内部函数,首先要把SIGCHLD信号阻塞住,以防出现竞争条件。
//子进程要解决信号阻塞,并执行相关的函数
//if the below code are outside of the buildin_cmd function,
//then these locks won't be realeased when executing inner commands
//block the SIGCHLD in order to prevent child process ends between father process
//先要阻塞SIGCHLD信号
sigprocmask(SIG_BLOCK,&mask_chld,&mask_prev);
//codes below won't be interrupt by signal SIGCHLD

//running a child process
//
if((pid=fork())==0){
//由于子进程会继承block的特性,所以子进程要记得unblock。
sigprocmask(SIG_SETMASK,&mask_prev,NULL);//unblock the order
//change the process 's group, not the same as tsh's group
setpgid(0,0);
if(execve(argv[0],argv,environ)<0){
printf("%s: Command not found\n",argv[0]);

}
//if execve cannot process then child process will execute main process
exit(0);
}

//blcok all signal
//为我阻挡一切!!就算天塌下来也要先addjob不然顺序乱就gg
sigprocmask(SIG_BLOCK,&mask_all,NULL);
//foreGround
if(!bg){
addjob(jobs,pid,FG,cmdline);
}else{
addjob(jobs,pid,BG,cmdline);
}
//block sigchld again
sigprocmask(SIG_SETMASK,&mask_chld,NULL);

//father process wait until front process stops


//父进程要判断子进程是前台进程还是后台进程,如果是前台进程,则调用waitpid来等待前台进程,如果是后台,把新添加进程利用addjob添加到工作组中。
if(!bg){
//Block until process pid is no longer the foreground process
waitfg(pid);
}
else{
sigprocmask(SIG_BLOCK,&mask_all,NULL);
struct job_t * currbgmask = getjobpid(jobs,pid);
printf("[%d] (%d) %s",currbgmask->jid,currbgmask->pid,currbgmask->cmdline);
}

//unblock all signals
sigprocmask(SIG_SETMASK,&mask_prev,NULL);
}

return;
}

builtin_command

注意访问全局变量jobs的时候要阻塞全部信号就是了

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
int builtin_cmd(char **argv)
{

sigset_t mask_all,mask_prev;

sigfillset(&mask_all);

if(!strcmp(argv[0],"quit")){
exit(0);
}
else if(!strcmp(argv[0],"&")){
return 1;
}
else if(!strcmp(argv[0],"jobs")){
//when visit a global variance,you need to block all signals
sigprocmask(SIG_BLOCK,&mask_all,&mask_prev);
listjobs(jobs);
sigprocmask(SIG_SETMASK,&mask_prev,NULL);
return 1;
}
else if(!strcmp(argv[0],"bg")||!strcmp(argv[0],"fg")){
do_bgfg(argv);
return 1;
}
return 0; /* not a builtin command */
}

waitfg

只要进程号一直是前台程序,就一直sleep等待

但奇怪的是,这个版本的waitfg函数运行有错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 
* waitfg - Block until process pid is no longer the foreground process
*/

void waitfg(pid_t pid)
{

//fgpid return the pid of the front process id

while((pid==fgpid(jobs))){
sleep(0);
}
return;
}

以下版本:
是书中545中介绍的一种显式接收信号的方法

只要信号处理函数回收了前台进程,它就会将fg_stop_or_exit(注意用volatile关键字声明) 置1,这样我们的waitfg函数就会退出,接着读取用户的下一个输入.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

/*
* waitfg - Block until process pid is no longer the foreground process
*/
void waitfg(pid_t pid)
{

sigset_t mask;
sigemptyset(&mask);
fg_stop_or_exit = 0;

////只有发出这个信号的子进程是前台进程才设置fg_stop_or_exit标志。
while(!fg_stop_or_exit){
sigsuspend(&mask);
}

return;
}

sigint_handler & sigtstp_handler

思路:

1.获取前台进程(fgpid),判断当前是否有前台进程,如果没有则直接返回,有则进行步骤2

2.使用kill函数,发送SIGINT/SIGTSTP信号给前台进程组

kill函数使用
1
2
3
int kill(pid_t pid,int sig);

//如果pid大于0,那么kill函数发送信号号码sig给进程pid,如果pid==0,那么kill发送信号sig给调用进程所在进程组中的每个进程,包括调用进程自己。如果pid<0,则发送sig给进程组|pid|中的每个进程。

代码如下

1.访问jobs的时候要阻塞所有信号

2.kill的pid是负的,说明发送信号对象是进程组,是所有前台程序

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

/*
* sigint_handler - The kernel sends a SIGINT to the shell whenver the
* user types ctrl-c at the keyboard. Catch it and send it along
* to the foreground job.
*/
void sigint_handler(int sig)
{

int olderrno = errno;
sigset_t mask_all,prev_all;
pid_t pid;
sigfillset(&mask_all);
//execute global function, so block all signals
sigprocmask(SIG_BLOCK,&mask_all,&prev_all);
pid=fgpid(jobs);
sigprocmask(SIG_SETMASK,&prev_all,NULL);

//only process the front process
//pid==0 means background process?

if(pid!=0){
kill(-pid,SIGINT);
//printf("Job [%d] (%d) terminated by signal %d\n",pid2jid(pid),pid,sig);
}

errno = olderrno;
return;
}

代码如下

1.注意如果进程已经停止,就不要再把它设置为停止了否则会出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

/*
* sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
* the user types ctrl-z at the keyboard. Catch it and suspend the
* foreground job by sending it a SIGTSTP.
*/
void sigtstp_handler(int sig)
{

pid_t pid;
pid=fgpid(jobs);

if(pid!=0){
struct job_t *job = getjobpid(jobs,pid);
if(job->state==ST)
return;
else
kill(-pid,SIGTSTP);
}

return;
}

sigchld_handler

status表示中止进程或者停止进程的原因,WNOHANG|WUNTRACED作用是判断当前进程中是否存在已经停止或者终止的进程,如果存在则返回pid,不存在立即返回

WIFSTOPPED(status):表示如果进程是因为停止的信号而停止,那么返回true

WIFSIGNALED(status):表示进程是因为捕获的信号而中止,返回true

WIFEXITED(status): 表示进程通过调用exit()或者return正常结束,则返回true。

参考:https://www.cnblogs.com/sky-heaven/p/8074273.html

filename already exists, rnamed

代码如下:

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
/*
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
* a child job terminates (becomes a zombie), or stops because it
* received a SIGSTOP or SIGTSTP signal. The handler reaps all
* available zombie children, but doesn't wait for any other
* currently running children to terminate.
*/

void sigchld_handler(int sig)
{
int olderrno = errno;
sigset_t mask_all,prev_all;
pid_t pid;
struct job_t *gc_job;
int status;

sigfillset(&mask_all);

//尽可能回收子进程,使用WNOHANG,使得如果当前进程都没有停止的时候直接返回,
//而不是挂起该回收进程,这样可能会阻碍无法两个短时间结束的后台进程

while((pid = waitpid(-1,&status,WNOHANG|WUNTRACED))>0){
sigprocmask(SIG_BLOCK,&mask_all,&prev_all);
gc_job = getjobpid(jobs,pid);
//说明当前引起停止的确实是前台进程
if(pid==fgpid(jobs)){
fg_stop_or_exit=1;
}
//子进程正常结束,返回一个非0值
if(WIFEXITED(status)){
deletejob(jobs,pid);
}
//子进程被暂停,只有暂停不用deletejobs
else if(WIFSTOPPED(status)){
//子进程停止引起waitpid函数返回,再判断该进程是否是前台进程
gc_job->state = ST;
printf("Job [%d] (%d) stopped by signal %d\n", gc_job->jid, gc_job->pid, WSTOPSIG(status));
}
//因捕获信号而终止
else if (WIFSIGNALED(status)){
//子进程终止引起的返回,判断是否是前台进程
//并且判断该信号是否是未捕获的的信号
printf("Job [%d] (%d) terminated by signal %d\n", gc_job->jid, gc_job->pid, WTERMSIG(status));
deletejob(jobs,pid);
}
fflush(stdout);
//unblock all signals
sigprocmask(SIG_SETMASK,&prev_all,NULL);
}
errno = olderrno;
return;
}

do_fgbg

1.输入时%num 代表jobsid,num代表进程id

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
/*
* do_bgfg - Execute the builtin bg and fg commands
*/
void do_bgfg(char **argv)
{

//parameters
char *para = argv[1];

//lack parameters
if(para==NULL){
printf("%s command requires PID or %%jobid argument\n",argv[0]);
return;
}

//full dirname
char *cmd = argv[0];

struct job_t*curr_job;
sigset_t mask_all,mask_prev;
int curr_jid;
sigfillset(&mask_all);

//first character of the paramaters
//linux command: fg %n bring process n from background to frontground
if(para[0]=='%'){

// the argument is a job id
curr_jid = atoi(&(para[1]));
//mistake process2
curr_job = getjobjid(jobs,curr_jid);
if(curr_job==NULL)
{
printf("%%%d: No such job\n",curr_jid);
return;
}
}

else{
// the argument is a process id
curr_jid = atoi(para);
if(curr_jid==0){
printf("%s: argument must be a PID or %%jobid\n",cmd);
fflush(stdout);
return;
}
//block all signals when visit global vairance
sigprocmask(SIG_BLOCK,&mask_all,&mask_prev);
curr_jid = pid2jid(curr_jid);
}

//block all signals when visit global vairance
sigprocmask(SIG_BLOCK,&mask_all,&mask_prev);
curr_job = getjobjid(jobs,curr_jid);

if(curr_job==NULL){
printf("(%s): No such process\n",para);
fflush(stdout);
sigprocmask(SIG_SETMASK,&mask_prev,NULL);
return;
}
//bg
if(!strcmp(cmd,"bg")){
switch(curr_job->state){
case ST:
//change from stop to bg ST->BG
//meanwhile send signal to child process
curr_job->state =BG;
kill(-(curr_job->pid),SIGCONT);
printf("[%d] (%d) %s",curr_job->jid,curr_job->pid,curr_job->cmdline);
break;
case BG:
break;
case UNDEF:
case FG:
unix_error("bg or undef error");
break;
}
}


//要用waitfg指令,等待前台作业结束后再退出
else{
switch(curr_job->state){
//如果作业本身是STOP的话,要记得发送信号(SIGCONT,让其继续运行)
case ST:
//change from stop to bg ST->BG
//meanwhile send signal to child process
curr_job->state =FG;
//发射信号给前台进程组,所有前台进程都会受到信号
kill(-(curr_job->pid),SIGCONT);
//if change to fg,then you need to wait until it dies
waitfg(curr_job->pid);
break;
case BG:
curr_job->state =FG;
waitfg(curr_job->pid);
break;
case UNDEF:
case FG:
unix_error("bg or undef error");
break;
}
}
sigprocmask(SIG_SETMASK,&mask_prev,NULL);
return;
}

总结

当我们在真正的shell(例如bash)中执行tsh时,tsh本身也是被放在前台进程组中的,它的子进程也会在前台进程组中,例如下图所示:

upload succsful

引用:

1.https://www.cnblogs.com/liqiuhao/p/8120617.html

2.https://blog.csdn.net/xiaolian_hust/article/details/80087376

DS-动态规划

Posted on 2018-12-09

Leetcode 647

Given an unsorted array of integers, find the length of longest continuous increasing subsequence (subarray).

Example 1:

Input: [1,3,5,4,7]
Output: 3
Explanation: The longest continuous increasing subsequence is [1,3,5], its length is 3.
Even though [1,3,5,7] is also an increasing subsequence, it’s not a continuous one where 5 and 7 are separated by 4.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int findLengthOfLCIS(vector<int>& nums) {
if(nums.empty())
return 0;
int n = nums.size();
int dp[n];
dp[0]=1;
int res = 1;
for(int i=1;i<nums.size();i++){
if(nums[i-1]<nums[i]){
dp[i] = dp[i-1]+1;
}
else{
dp[i] = 1;
}
res = max(dp[i],res);
}
return res;
}

printLIS 路径

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

//M saves the index
int M[MAX+1];
int pre[MAX+1];
int pathIndex[MAX+1];

vector<int>vec;

void print(int pos){
if(pos==-1)return;
print(pre[pos]);
cout<<" "<<vec[pos];
}

int main(){

ios::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);

int N;
while(true){
cin>>N;
if(N==0)
break;
vec.assign(N,0);
for(int i=0;i<N;i++){
cin>>vec[i];
}
int L = 0;
for(int i=0;i<N;i++){
int lo = 1;
int Hi = L;
//find the largest mid <=L such that vec[M[mid]]<vec[i]
while(lo<=Hi){
int mid = ceil((lo+Hi)/2.0);
if(vec[M[mid]]<vec[i]){
lo=mid+1;
}else{
Hi = mid-1;
}
}

//newL is 1 greater than the longest prefix of vec[i]
int newL = lo;
//vec[i]的前驱节点是newL-1子串的最后一个索引
pre[i] = M[newL-1];
//NEWL新子串的最后一个索引就是i
M[newL] = i;

if(newL>L)
L = newL;
}

int k = M[L];
for(int i=L-1;i>=0;i--){
pathIndex[i] = k;
k = pre[k];
}
cout<<L<<" ";
for(int i=0;i<=L-1;i++){
cout<<vec[pathIndex[i]]<<" ";
}
cout<<endl;


}

}

Leetcode 300

Given an unsorted array of integers, find the length of longest increasing subsequence.

Example:

Input: [10,9,2,5,3,7,101,18]
Output: 4
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int lengthOfLIS(vector<int>& nums) {

if(nums.empty())
return 0;
int n = nums.size();
int dp[n];
for(int i=0;i<n;i++)
dp[i]=1;
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
//仅当dp[j]+1>dp[i]的时候才更新
if(dp[j]+1>dp[i]){
dp[i]=dp[j]+1;
}
}
}
}
int res = 0;
for(int i=0;i<n;i++)
res = max(res,dp[i]);
return res;
}

复杂度:O(n^2)

解析:

https://www.youtube.com/watch?v=CE2b_-XfVDk

以下是O(nlogn)作法

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

int lengthOfLIS(vector<int>& nums) {

if(nums.empty())
return 0;
int n = nums.size();
int dp[n];
dp[0] = nums[0];
int len=1;
for(int i=1;i<n;i++){
int left=0;
int right = len-1;
int mid;
if(dp[len-1]<nums[i]){
dp[len++]=nums[i];
}else{
while(left<=right){
mid = (left+right)/2;
if(dp[mid]<nums[i]){
left = mid+1;
}else{
right = mid-1;
}
}
dp[left] = nums[i];
}

}

return len;
}
};

dp[i]表示长度为i的LIS序列的最后一个数字最小末尾

https://www.cnblogs.com/ziyi--caolu/p/3227121.html

leetcode 53

Longest maximum subarray

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

int max_sum(vector<int>&nums,int i){
if(i==0)
return nums[i];
int res = max(nums[i],nums[i]+max_sum(nums,i-1));
return res;
}

int maxSubArray(vector<int>& nums) {

if(nums.empty())
return 0;
int res = nums[0];
for(int i=0;i<nums.size();i++){
res = max(res,max_sum(nums,i));
}
return res;


}

方法二:

转移方程: dp[i] = nums[i]+(dp[i-1]>0?dp[i-1]:0);

1
2
3
4
5
6
7
8
9
10
11
12
13
14

int maxSubArray(vector<int>& nums) {
if(nums.empty())
return 0;
int n = nums.size();
int dp[n];
dp[0] = nums[0];
int res = dp[0];
for(int i=1;i<n;i++){
dp[i] = nums[i]+(dp[i-1]>0?dp[i-1]:0);
res = max(res,dp[i]);
}
return res;
}

拓展

给定K个整数的序列{ N1, N2, …, NK },其任意连续子序列可表示为{ Ni, Ni+1, …,
Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个,
例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和
为20。
在今年的数据结构考卷中,要求编写程序得到最大和,现在增加一个要求,即还需要输出该
子序列的第一个和最后一个元素。

http://acm.hdu.edu.cn/showproblem.php?pid=1231

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
#include <iostream>
#include <cstring>

using namespace std;
#define INF 0x3f3f3f3f
int A[30][30][30];

int main(){

int k;

while(true){
long long int dp[10001];
int arr[10001];
cin>>k;
if(k==0)
break;
int posFlag = 0;
for(int i=1;i<=k;i++){
scanf("%d",&arr[i]);
if(arr[i]>=0)
posFlag=1;
}
if(posFlag!=1){
cout<<0<<" "<<arr[1]<<" "<<arr[k]<<endl;
}
else{
double max = -INF;
dp[1]=arr[1];
int resulti=1;
for(int i=1;i<=k;i++){
//or dp[i] = max(dp[i-1]+arr[i],arr[i])
dp[i] = arr[i]+(dp[i-1]>0?dp[i-1]:0);
if(dp[i]>max){
max = dp[i];
resulti = i;
}
}
double max2=0;
int start=1;
for(int i=resulti;i>=1;i--){
max2+=arr[i];
if(max2==max){
start = i;
break;
}
}
cout<<max<<" "<<arr[start]<<" "<<arr[resulti]<<endl;
}

}

}

关键是要找到子序列的第一个元素。

当找到最后一个元素的索引时候,从索引处由后往前找,知道sum等于结果的max,记录下此时的索引i,于是便找到了第一个元素的索引i

Longest common subsequence

upload successul

转移方程 : res = max(lcs(i-1,j),lcs(i,j-1),1+lcs(i-1,j-1))

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

string a="bananinn";
string b = "kaninan";

int lcs(int i,int j){
if(i==-1||j==-1)
return 0;


int res=0;
res = max(res,lcs(i-1,j));
res = max(res,lcs(i,j-1));

if(a[i]==b[j]){
res = max(res,1+lcs(i-1,j-1));
}
return res;
}

int main(){
int mem[1000][1000];
memset(mem,-1,sizeof(mem));
int ans = 0;
for(int i=0;i<8;i++){
for(int j=0;j<7;j++){
ans = max(ans,lcs(i,j));
}
}
cout<<ans<<endl;
}

DP模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Dynamic programming formulation
map<problem, value> memory;
value dp(problem P) {
if (is_base_case(P)) {
return base_case_value(P);
}
if (memory.find(P) != memory.end()) {
return memory[P];
}
value result = some value;
for (problem Q in subproblems(P)) {
result = combine(result, dp(Q));
}
memory[P] = result;
return result;
}

Traveling Salesman problem

filename already existsamed

TSP问题就是在一张有权图,从某个起点出发,然后go through each node exactly once and finally return to the beginning node,such that the weightSum is minimal

NP-HARD

no solution with dominomial time complexity

upload succsful

子问题一共有 2^n*n 个,每个子问题用O(n)复杂度来解决

T:(2^n*n^2)

0-1背包问题

状态转移方程:

1
2
3
4
5
//f[i][j]表示i个物品,容量j背包的物品最大价值
//若f[i][j] == f[i-1][j],说明不装第i个
//否则装入第i个,同时容量-w,价值+v,w,v分别为第i个物品的重量和价值

f[i][j] = max(f[i-1][j],f[i-1][j-w]+v);

kattis - knapsack

关键是如何打印出装入的物品的index

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
int main(){
double capa;
int n;
while(cin>>capa&&cin>>n){
int capacity = floor(capa);
vector<vector<int>>f(n+1,vector<int>(capacity+1,0));
values.assign(n+1,0);
weights.assign(n+1,0);
for(int i=1;i<=n;i++){
int v,w;
cin>>v>>w;
weights[i] = w;
values[i] = v;
}

for(int i=1;i<=n;i++){
int v = values[i];
int w = weights[i];
for(int j=1;j<=capacity;j++){
if(j<w){
f[i][j] = f[i-1][j];
continue;
}
f[i][j] = max(f[i-1][j],f[i-1][j-w]+v);
}
}

//打印最终装入的物品的index! 即寻找f[i][j]!=f[i-1][j]
vector<int>res;
int j = capacity;
for(int i=n;i>=1;i--){
if(f[i][j]!=f[i-1][j]){
res.push_back(i-1);
j-=weights[i];
}
}
cout<<res.size()<<endl;
for(auto i:res)
cout<<i<<" ";
cout<<endl;
}
}

http://poj.org/problem?id=1579

记忆化搜索

把递归结果存在表里,减少不必要的递归次数

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
int A[30][30][30];

int w(int a,int b,int c){
if(a<=0||b<=0||c<=0){
return 1;
}
if(a>20||b>20||c>20){
return w(20,20,20);
}
if(A[a][b][c]) //if already in A no need to do recursion
return A[a][b][c];
if(a<b&&b<c){
A[a][b][c] =w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c); //A[a][b][c-1]+A[a][b-1][c-1]-A[a][b-1][c];
}
else
A[a][b][c] = w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1);//A[a-1][b][c]+A[a-1][b-1][c]+A[a-1][b][c-1]-A[a-1][b-1][c-1];
return A[a][b][c];
}

int main(){

memset(A,0,sizeof(A));
while(true){
int a,b,c;
cin>>a>>b>>c;
if(a==-1&&b==-1&&c==-1)
break;
cout<<w(a,b,c)<<endl;
}

}

dfs solve TSP

https://open.kattis.com/problems/beepers

dfs解TSP问题

基本思路是从出发点开始,尝试从出发点到其他所有点的可能性,然后回溯。

dfs的大概思路就是当n<num的时候,尝试到不同的点,回溯,当n==num的时候,比较现在的路径长度是否小于最短路径。

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
int shortest,num;
int startx,starty;
struct node{
int x;
int y;
};
vector<node>vec;
int visited[MAX];

int dis(int x1,int y1,int x2,int y2){
return abs(x1-x2)+abs(y1-y2);
}

void DFS(int cur,int len,int n){

if(n==num){
int t = dis(vec[cur].x,vec[cur].y,startx,starty);
if(len+t<shortest){
shortest = len+t;
}
}else if(len<shortest){
int i;
for(i=0;i<num;i++){
if(visited[i]==0){
visited[i] = 1;
DFS(i,len+dis(vec[cur].x,vec[cur].y,vec[i].x,vec[i].y),n+1);
visited[i] = 0;
}
}
}


}


int main(){

ios::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);


int sc;
int row,col;
cin>>sc;
while(sc--){
cin>>row>>col;
cin>>startx>>starty;
cin>>num;
vec.clear();
for(int i=0;i<num;i++){
int xx,yy;
cin>>xx>>yy;
vec.push_back({xx,yy});
}
shortest = INF;
memset(visited,0,sizeof(visited));

for(int i=0;i<num;i++){
int tempL = dis(vec[i].x,vec[i].y,startx,starty);
visited[i] = 1;
DFS(i,tempL,1);
visited[i] = 0;
}
cout<<shortest<<endl;
}
}

leetcode 931

minimum falling path sum

upload scessful

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

int minFallingPathSum(vector<vector<int>>& A) {
int res = INT_MAX;
int row = A.size();
int col = A[0].size();
int dp[row][col+2];

for(int i=1;i<row;i++){
for(int j=0;j<col;j++){
if(j==0){
A[i][j] = A[i][j]+min(A[i-1][j],A[i-1][j+1]);
}
else if(j==col-1){
A[i][j] = A[i][j]+min(A[i-1][j],A[i-1][j-1]);
}
else
A[i][j] = A[i][j]+min(A[i-1][j],min(A[i-1][j-1],A[i-1][j+1]));
}
}

for(int i=0;i<col;i++){
res = min(res,A[row-1][i]);
}
return res;

}

复杂度 n^2

CSAPP-异常控制流

Posted on 2018-12-07 | In CSAPP

CPU所执行的指令的地址序列称为CPU的控制流,通过下述两种方式得到的控制流为正常控制流。

1.按顺序取下一条指令执行。

2.通过CALL/RET/Jcc/JMP等指令跳转到转移目标地址处执行

异常控制流

upload sucessful

硬件层面有两种情况:

1.执行指令的硬件发现指令有异常。eg:除0

2.外部中断 ctrl+c

异常控制流形成原因(1.2硬件层面)

1.内部异常:缺页,越权,越级,整除0,溢出等,都是CPU可以发现的。

2.外部中断(Ctrl-C,打印缺纸,DMA结束等)由外界请求信号通知CPU

3.进程的上下文切换(发生在操作系统层)

4.一个进程直接发送信号给另外一个进程(发生在应用软件层)

程序和进程

uplod successful

upload sucessful

pload successful

vm_area_struct 是一个线性链表

引入进程的好处

ilename already exists, renamed

独立的逻辑控制流意味着进程不会感觉到其他进程的存在,使得其不容易受其他进程打乱

逻辑控制流

filename eady exists, renamed

进程p1,A12,打断一次

进程p2,A24,打断一次

进程与上下文切换

什么叫进程的上下文?
upload sucessful

upload succeul

用户级上下文地址空间和系统级上下文地址空间一起构成了一个进程的整个存储器映像

进程

以下内容引用
https://wdxtub.com/2016/04/16/thin-csapp-5/

进程才是程序(指令和数据)的真正运行实例。之所以重要,是因为进程给每个应用提供了两个非常关键的抽象:一是逻辑控制流,二是私有地址空间。逻辑控制流通过称为上下文切换(context switching)的内核机制让每个程序都感觉自己在独占处理器。私有地址空间则是通过称为虚拟内存(virtual memory)的机制让每个程序都感觉自己在独占内存。这样的抽象使得具体的进程不需要操心处理器和内存的相关适宜,也保证了在不同情况下运行同样的程序能得到相同的结果。

filename already exists, ramed

左边是单进程的模型,内存中保存着进程所需的各种信息,因为该进程独占 CPU,所以并不需要保存寄存器值。而在右边的单核多进程模型中,虚线部分可以认为是当前正在执行的进程,因为我们可能会切换到其他进程,所以内存中需要另一块区域来保存当前的寄存器值,以便下次执行的时候进行恢复(也就是所谓的上下文切换)。整个过程中,CPU 交替执行不同的进程,虚拟内存系统会负责管理地址空间,而没有执行的进程的寄存器值会被保存在内存中。切换到另一个进程的时候,会载入已保存的对应于将要执行的进程的寄存器值。

我们所讲的“双核”

upload success

upload successfu

上下文切换是指把运行内核代码的环境调出来,然后把用户代码的环境(PC,寄存器等)保存起来

进程地址空间

虚拟地址空间由内核空间和用户空间两部分组成。用户空间(32位)都从0x08048000组成。
upload successfl

进程控制

在遇到错误的时候,Linux 系统级函数通常会返回 -1 并且设置 errno 这个全局变量来表示错误的原因。使用的时候记住两个规则:

1.对于每个系统调用都应该检查返回值
2.当然有一些系统调用的返回值为 void,在这里就不适用

fork函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void unix_error(char *msg) /* Unix-style error */
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}


pid_t Fork(void)
{
pid_t pid;
if ((pid = fork()) < 0)
unix_error("Fork error");
return pid;
}

获取进程信息

我们可以用下面两个函数获取进程的相关信息:

1.pid_t getpid(void) - 返回当前进程的 PID
2.pid_t getppid(void) - 返回当前进程的父进程的 PID

我们可以认为,进程有三个主要状态:

1.运行 Running
正在被执行、正在等待执行或者最终将会被执行
2.停止 Stopped
执行被挂起,在进一步通知前不会计划执行
3.终止 Terminated
进程被永久停止

用户态和内核态

filename alredy exists, renamed

程序的加载和运行

upload succesful

upload successl

filename alrdy exists, renamed

upload succesul

entry point 是可执行目标文件ELF头 的entry point

所以程序的加载和运行就是一个进程切换到另外一个进程,中间要进行上下文切换。切换新进程的时候先要创建一个进程(fork),然后exec,然后运行main

upload successful

第一个参数先压栈,最后一个参数最后压栈,注意上图,argv是一个指针,指向一个数组,即图中argv【0】处,每一个元素又本身是一个指针,指向一个字符串,envp也是一个指针数组,每一个元素指向一个环境变量。

然后如果main函数调用了其它函数,就会又长出一个栈帧,这就是程序加载与运行的过程。

信号

可以用kill函数发射信号

子进程陷入无限循环,则父进程发射KILL信号,终结子进程。

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
void forkandkill()
{
pid_t pid[N];
int i;
int child_status;

for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
while(1) ; // 死循环

for (i = 0; i < N; i++)
{
printf("Killing process %d\n", pid[i]);
kill(pid[i], SIGINT);
}

for (i = 0; i < N; i++)
{
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",wpid,WEXITSTATUS(child_status));
else
printf("Child %d terminated abnormally\n", wpid);
}
}

接收信号

所有上下文切换都是通过调用某个异常处理器(exception handler)完成的,内核会计算对易于某个进程p的pnb值:pnb=pending&~blocked

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
void unix_error(char *msg) /* Unix-style error */
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}


void sigint_handler(int sig) // SIGINT 处理器
{
printf("想通过 ctrl+c 来关闭我?\n");
sleep(2);
fflush(stdout);
sleep(1);
printf("OK. :-)\n");
exit(0);
}
int main()
{
// 设定 SIGINT 处理器
if (signal(SIGINT, sigint_handler) == SIG_ERR)
unix_error("signal error");

// 等待接收信号
pause();
return 0;
}

upload succeful

信号处理器的工作流程可以认为是和当前用户进程“并发”的同一个伪进程。

并行与并发的区别

并行:多个CPU同时执行程序

并发(concurrent):即使只有一个CPU,但操作系统能够把程序的执行单位细化,然后分开执行。是一种伪并行执行

阻塞信号

内核会阻塞与当前在处理的信号同类型的其他正在等待的信号,也就是说一个SIGINT信号处理是不能被另外一个SIGINT信号中断的。

如果要显示阻塞,需要用sigprocmask函数

1
2
3
4
5
6
7
8
9
10
sigset_t mask, prev_mask;
Sigemptyset(&mask); // 创建空集
Sigaddset(&mask, SIGINT); // 把 SIGINT 信号加入屏蔽列表中
// 阻塞对应信号,并保存之前的集合作为备份
Sigprocmask(SIG_BLOCK, &mask, &prev_mask);
...
... // 这部分代码不会被 SIGINT 中断
...
// 取消阻塞信号,恢复原来的状态
Sigprocmask(SIG_SETMASK, &prev_mask, NULL);

并行访问可能会导致数据毁坏问题,以下是一些编写程序的规则。

规则 1:信号处理器越简单越好
    例如:设置一个全局的标记,并返回
规则 2:信号处理器中只调用异步且信号安全(async-signal-safe)的函数
    诸如 printf, sprintf, malloc 和 exit 都是不安全的!
规则 3:在进入和退出的时候保存和恢复 errno
    这样信号处理器就不会覆盖原有的 errno 值
规则 4:临时阻塞所有的信号以保证对于共享数据结构的访问
    防止可能出现的数据损坏
规则 5:用 volatile 关键字声明全局变量
    这样编译器就不会把它们保存在寄存器中,保证一致性
规则 6:用 volatile sig_atomic_t 来声明全局标识符(flag)
    这样可以防止出现访问异常

异步信号安全:指的是如下两类函数:

1.所有的变量都保存在栈帧当中
2.不会被信号中断的函数

非本地跳转 Non local jump

从一个函数跳转到另一个函数中

1
2
3
4
setjmp 保存当前程序的堆栈上下文环境(stack context),注意,这个保存的堆栈上下文环境仅在调用 setjmp 的函数内有效,如果调用 setjmp 的函数返回了,这个保存的堆栈上下文环境就失效了。调用 setjmp 的直接返回值为 0。


longjmp 将会恢复由 setjmp 保存的程序堆栈上下文,即程序从调用 setjmp 处重新开始执行,不过此时的 setjmp 的返回值将是由 longjmp 指定的值。注意longjmp 不能指定0为返回值,即使指定了 0,longjmp 也会使 setjmp 返回 1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jmp_buf env;
P1()
{
if (setjmp(env))
{
// 跳转到这里
} else
{
P2();
}

}
P2()
{
...
P2();
...
P3();
}
P3()
{
longjmp(env, 1);
}

uploadccessful

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
jmp_buf env;
P1()
{
P2(); P3();
}
P2()
{
if (setjmp(env))
{
// 跳转到这里
}
}
P3()
{
longjmp(env, 1);
}

因为P2在跳转的时候,已经在P3前返回了,内存已经清理了其对应的栈帧,所以P3的longjmp不能实现期望的操作。

CSAPP 家庭作业

waitpid函数的作用:

当指定等待的子进程以及停止运行或者结束了,waitpid函数会立即返回,如果子进程还没有停止运行或者结束,调用waitpid的父进程会被祖塞,暂停运行

8.18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void end(){

printf("2");fflush(stdout);
}

int main()
{
if(fork()==0)
atexit(end);
if(fork()==0){
printf("0");fflush(stdout);
}
else{
printf("1");fflush(stdout);
}
exit(0);
}

第一个子进程的atexit函数把end函数添加到函数列表中,那么这个子进程生成的两个子进程的堆栈中也会有end函数,但是另外父进程则独立,不受影响,即使wxit也不会有反应。

CSAPP-IA-32/linux中的地址转换

Posted on 2018-12-07 | In CSAPP

filename alrady exists, renamed

upload succeful

1.逻辑地址-》线性地址是 分段
2.线性地址-》内存地址是 分页

这里的8是偏移量A
%ebp是基址寄存器B,%esp是变址寄存器I,4是比例因子S

分段过程

线性地址的计算

upload sucsful

段选择符是在上图的段寄存器里面的

upload success

段选择符就相当于index位,分段方式中有一个”段表“(在主存),段表记录了段的长度,段开始的地方,存取权限等。

线性地址 = 段基址+有效地址

有效地址 = 基址寄存器+变址寄存器×比例因子

有效地址实际上是一个段内的偏移量,首地址+段内偏移量 = 线性地址。

upload succesful

得到的线性地址再通过分页,通过页表转换为主存地址

段寄存器的含义

filename already exists, rend

RPL: 最后两位表示管理状态(request privilege level)

filename alreadmed

段描述符和段描述符表

filename already exists, renamed

什么叫任务状态段?

这里的任务指的是进程,就是进程执行到某个阶段的时候,CS.SS.EIP,ESP,GPR内容等信息(代码算段,堆栈段,指令指针,栈指针,通用寄存器内容等)

中断门描述符记录了处理中断等异常处理的程序的首地址

upload successl

filename already enamed

为了减少从主存中找段描述符,使用cache

upload succsful

upload suessful

TSS描述符在进行进程切换的时候,TR里面的内容也要进行切换。

所有表的起始地址是放在GDR,而所有异常中断程序的首地址放在IDT,IDT的首地址也放在IDTR里面

1
GDT和IDT只有一个,GDTR 和 IDTR指向各自起始的地方,根据TR取GDT中的TSS描述符的时候,GDTR给出首地址

Linux的全局段描述符表

upload sucssful

最后两位是RPL,都为0表示其在第0环,位于内核

倒数第三位表示这个描述符在GDT中,剩下的位便是索引号,
TSS索引是0x0010,即全局描述符表的第16项,LDT索引是0x0011,是第17项

因此在上上图中,操作系统会分别把0x80与0x88放在TR与LDTR中

逻辑地址向线性地址转换

upload succesful

逻辑地址(48位)=》 线性地址(32位)

16位的段选择符,根据TI选择去GDT还是去LDT,其中GDT,LDT的首地址分别存储在GDTR和LDTR里面,(不可见寄存器)

段描述符 = 首地址+8×段选择符的索引

所以得到的段基地址加上段偏移量,就得到32位线性地址

段描述符只有在第一次进行访问,访问过后就放在了cache里面,所以之后求线性地址,只需要在cache里面取基地址然后相加就行

upoad successful

1.就是初始化时候上述4个段描述符放在GDT中
2.每个段都被初始化在0-4GB的线性地址空间中

uploauccessful

filename already exists, reamed

有效地址(偏移量) = 基址寄存器+变址寄存器×比例因子

filename alreay exists, renamed

所以线性地址就等于EA = 有效地址

线性地址 = 基地址+有效地址

所谓EA其实只是一个segment内的一个段内偏移量

upload succeul

指令的线性地址 = 代码段基地址+有效地址,而linux编译器默认了代码段基地址位0,因此指令的线性地址就等于有效地址就等于段的偏移量。

而数据的线性地址 = 数据段基地址+EA =0+ R[ECX]+R[EDX]*4

0X8048A00+(400)的16进制,注意进制的转换

分页过程(线性地址-》物理地址)

如果页大小为4KB,每个页表项占4B则理论上一个页表大小:

项个数:2^32/2^12 = 2^20 ,因此每个页表大小位4MB

比页还要大,因此采用多级页表方式来存储

—————————————————————————————————————————

filename already exists, red

IA-32采用二级页表方式来存储

upload sucssful

每个页表起始位置按4KB对齐意思就是每个页表起始的20位都是相同的,只有后面的12位(4KB)不同

upload

upload succeul

MMU 完成从逻辑地址到线性地址的过程

CPL,RPL,DPL区别与联系

1.CPL是当前进程的权限级别(Current Privilege Level),是当前正在执行的代码所在的段的特权级,存在于cs寄存器的低两位。   

2.RPL说明的是进程对段访问的请求权限(Request Privilege Level),是对于段选择子而言的,每个段选择子有自己的RPL,它说明的是进程对段访问的请求权限,有点像函数参数。而且RPL对每个段来说不是固定的,两次访问同一段时的RPL可以不同。RPL可能会削弱CPL的作用,例如当前CPL=0的进程要访问一个数据段,它把段选择符中的RPL设为3,这样虽然它对该段仍然只有特权为3的访问权限。     

3.DPL存储在段描述符中,规定访问该段的权限级别(Descriptor Privilege Level),每个段的DPL固定。
当进程访问一个段时,需要进程特权级检查,一般要求DPL >= max {CPL, RPL}

下面打一个比方,中国官员分为6级国家主席1、总理2、省长3、市长4、县长5、乡长6,假设我是当前进程,级别总理(CPL=2),我去聊城市(DPL=4)考察(呵呵),我用省长的级别(RPL=3 这样也能吓死他们:-))去访问,可以吧,如果我用县长的级别,人家就不理咱了(你看看电视上的微服私访,呵呵),明白了吧!为什么采用RPL,是考虑到安全的问题,就好像你明明对一个文件用有写权限,为什么用只读打开它呢,还不是为了安全!

ref:https://blog.csdn.net/better0332/article/details/3416749

CSAPP-Virtural Memory

Posted on 2018-12-06

虚拟内存

为每个进程提供了一个大的,一致的和私有的地址空间。

1.把主存看成是一个存储在磁盘上的地址空间的高速缓存

2.为每个进程提供了一致的地址空间,简化了内存管理

3.保护每个进程的地址空间不被其他进程破坏

早期分页方式的概念

大白话:程序是在逻辑空间上写的,而是在物理空间上运行,所以要解决逻辑空间与物理空间之间的映射。

upload successf

就是程序可能会有2^16byte长,但是物理容量只有4K

filename alrady exists, renamed

这个区号意思就是说,地址空间0-4095对应page 1,4096-8192对应page2

区间就是页(page),主存中存放页的区域成为页框

分页

upload succesul

upad successful

逻辑地址 1:30 1作为地址空间索引号(页号),30作为内容

14代表页框号

所以页表就是用来映射页号和物理地址中的页框号

upload successl

页框之间内容可以离散,页框内部内容连续

虚拟存储系统

大白话:程序可能很长,但是主存容量有限,于是把活跃的程序段放到主存中运行,然后自动进行切换

实质:

1.程序员在比实际主存空间大得多的逻辑地址空间中编写程序

2.程序执行的时候,要把当前需要的程序段以及相应的数据块调入主存当中,不用的部分放在磁盘上

虚拟存储技术的实质

进程调入物理主存的空间(程序中活跃的片段占用了主存的物理空间)

upload successl

每个程序会有一个页表来说明它这一页的情况
(在磁盘上/内存上/空页)

upload successl

cpu拿到的是虚拟地址,要求转化为主存地址,而这个过程是在执行指令的时候完成的,只能通过硬件来完成

filename already exists, med

加载一个可执行文件,执行的时候,不会实际上把数据都加载到内存当中,而是生成该程序对应的一个页表。然后按照按需调页原则,通过pageFault异常来实现,把数据装入到内存当中。

主存–磁盘

upload successl

因为访问磁盘的时间会比较长,因此要尽可能减少磁盘的次数

write back 只写主存,不写磁盘

页表

用结构数组来实现

upload successfu

装入位->valid bit

修改位->dirty bit

替换控制位->根据其使用情况,决定该page是淘汰掉还是保留
eg(LRU.LMU)

实页号->页框号

每个进程有一个页表,其中有装入位,dirty位,替换控制位,访问权限位(确定是否可读/可写)还有禁止缓存位,实页号

一个页数的项数有什么来决定?虚拟地址空间大小来决定

项数 = 虚拟地址空间大小 / 页大小

4GB/4KB = 2^20 项

每个进程的页表大小一样吗?

各个进程有相同虚拟空间,所以理论上一样。实际大小看具体实现方式,如“空洞:页面如何处理

fil

页表分三种

upload successl

逻辑地址转换为物理地址

upload success

VA:VIRTUAL ADDRESS

类比于全相连cache,地址只有两部分,高位的tag,低位的偏移量。这里也是类似,page no,表示页表索引,页表基址存放在一个寄存器当中,页表的地址 = 基址+页表索引×页框大小

PF: frame no. ,即物理页号,其加上disp偏移量就等于PA,物理地址。

V = 0时,发生缺页。

异常情况

这个时候就需要操作系统来帮忙处理

upload success

TLB(快表)

upload sssful

全相连中,tag = 标签号,没有组号

组相连当中,虚页号高位作为tag,低位作为组号(index)

upload succe

1.从virtual page中得到虚页号,把虚页号与TLB中的tag作比较,如果valid=0 or valid = 1 but tag!=VA,then 发生缺页。

2.于是从主存的页表(慢表)开始寻找,由于页表中的表项是按照索引号排列的,所以并不需要tag号。如果V=0,还找不到,则时候就要到外存当中的磁盘找对应的表项。

3.如果找到了,可以直接从page frame中生成物理地址号,这样就不需要访问向前面部分介绍的那样,访问主存。

所以引入快表(在cache)目的就是减少主存的访问次数,提高逻辑地址与物理地址转换的速度。

upload successfl

最理想路径: VA->HIT1->HIT3->CPU 不用访问主存

如果发生了TLB缺失(miss1),在主存中的的页表中寻找,如果找到了就是hit2,继续送到cache里面,否则就是miss 2 ,发生page fault

如果送到cache,cache miss了,就发生miss 3,从主存那里找。

示意图

filename already exists, renaed

最上面那一段属于页表,中间段属于TLB,下面那一段是cache

大概过程:虚拟地址从TLB表中寻找,如果找不到就去页表找,然后映射为物理地址后,物理地址在cache中找,找不到就访问主存,从主存中找。

虚拟地址分为20位的虚拟页号,12位的页内地址

上图中由于是组相连,因此虚拟页号分为tag标记以及组索引,当v=1而且TLB标记等于标记号的时候,就找到了物理页号,然后物理页号与虚拟地址的业内地址结合起来,就找到了物理地址。然后物理地址就又分为标记号,组索引以及块内地址,于cache中寻找所要寻找的字节。

upload successl

缺页处理是由操作系统来作的

upload ful

如果不在页表当中,就绝对不可能再快表当中;如果页表缺失,说明信息不在主存,cache也一定不可能有!

三个都miss过程:

1.快表miss,去页表(在主存)中找,访存1次

2.页表miss,只能从磁盘中寻找,访问磁盘一次

3.cache miss,从主存当中寻找,访存第二次

一个例子

upload succesul

页表项数: 虚拟地址大小 / 页大小

upload succesl

虚拟地址中,高8位就是虚拟页号,低6位就是偏移量。

物理地址中,高6位是实页号,低6号是偏移量。

快表:

upload successl

16个TLB项,4路组相连,说明TLBI是2,剩下6位就是tag位

快表中0A34数据有误,因为其VPN就是tag+set号

tag是0A,set是03,所以VPN为 00101011 就是0x2B

但是在慢表中,0x2Bvalid位是0,所以说明数据有误

cache:

upload succesful

一共有16行,所以CI是4,偏移量是2(主存块4B)

upload succesul

VPN = 0xF TLBI = 0x3 TLBT = 0x3

即在快表的第三组中找tag==3的,找到了,说明其命中,没有page fault,PPN是0D

0D = 1101

所以物理地址为(PPN+VPO)
001101010100

C0 = 00
CI = 0101
CT = 001101

在cache中找第5行,tag为001101的16位数据

B0 = 36 B1 = 72

所以要找的数据是7236(小端,高位数据在高位)

分段式虚拟存储器

分段方式很好管理,但是占的空间多,储存空间管理不好管理。

oad successful

file already exists, renamed

段页式存储器

upload succful

练习题9.5
1
2
3
4
5
6
7
8
9
10
#include <unistd.h>
#include <sys/mman.h>
char *bufp

//fd 为open函数返回的文件描述符
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset)


//创建一个包含size字节的只读,私有,请求二进制0的虚拟内存区域,如果调用成功,bufp则为新区域的地址
bufp = Mmap(NULL,size,PROT_READ,MAP_PRIVATE|MAP_ANON,0,0);

用以上函数,讲一个任意大小的磁盘文件复制到stdout,文件名作为一个命令行参数传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <csapp.h>

void mmapcopy(int fd,int size){
char *bufp;
bufp = Mmap(NULL,size,PROT_READ,MAP_PRIVATE,fd,0);
Write(1,bufp,size);
return;
}


int main(int argc,char **argv){
if(argc!=2){
printf("usage: %s <filename>\n",argv[0]);
}

int fd;
struct stat stat;
fd = Open(argv[0],O_RDONLY,0);
fstat(fd,&stat);
mmapcopy(fd,stat.st_size);
exit(0);
}

//文件描述符的用法
//https://blog.csdn.net/u012736748/article/details/74626145

文件描述符是一个索引,指向内核为每一个进程所维护的该进程打开文件的记录表。

删除虚拟地址

1
2

int mummap(void *start,size_t length);

动态内存分配

usage of malloc package

upload scessful

fildy existsenamed

可以通过诸如malloc,new等方法让程序运行的时候得到虚拟内存。动态内存分配器会管理一个虚拟内存区域,叫做堆。

分配器以块为单位来维护堆,可以进行分配或者释放。有两种类型:

1.显式分配器:应用分配并且回收空间(malloc & free)
2.隐式分配器,只负责分配,但是不负责回收(JAVA中的垃圾收集)

过程如下:

upload

upload

性能指标

假设给定一个malloc和free的请求的序列目标是尽可能提高吞吐量和内存利用率。

吞吐量: 单位时间内完成的请求数量。

内存利用率:主要影响因素是内存碎片。

内部碎片

upld successful

外部碎片

upload ssful

Peak Memory Utilization

upload susful

implementation

how to free

upload successf

pointer 前面用一个word记录block size

free的时候,访问指针前一个word就可以知道blocksize了

keeping track of free blocks

uploadsuccessful

implicit free lists

filename alredy exists, renamed

因为每一个block是8-byte alignment的,因此最后3位必定都是0,所以可以利用最后一位来存储该块是否已经分配

example

filenamey exists, renamed

Finding a free block

filename already exists, renamd

这里的 p & -2 -> p & 1111111…110 即得到block的size ,i.e mask out the last bit

allocating in free block

upload succesul

freeing a block

upload cessful

Coalescing

upload succsful

next pointer points to where 2 stores

如果2开头的block是空的,那么 p = p +*next

意思就是说4 = 4+2 = 6

Explicit Free Lists

upload succeful

一些区别

explicit empty list & implicit empty list

隐式的话,分配时间是块总数线性时间,而显式的话,是空闲块数量的线性时间,一般来说显式会比隐式更快。

first fit && next fit && best fit

首次适配:从头开始搜索空闲链表,选择第一个合适的空闲块。

下一次适配:从上一次搜索结束的位置开始搜索。

最佳适配:检索每一个空闲块,选择适合需求的最小空闲块。

Stack Pointer/Frame Pointer

Posted on 2018-12-06

Stack Frame

fil

firstly, callee stack save the old caller ‘s stack ‘s ebp

lastly, the argument build means the arguments for calling any other function

upload successfl

pushl $zip2,$zip1 把参数压进栈顶,然后call swap,会把caller的return address压入栈

whole code

upload succesful

set up code

filename already exists, amed

一. push %ebp 把caller的 bast pointer 的数值压栈

二.movl %esp,%ebp (把esp 移动到ebp,即设置ebp = esp,这一步是设置callee的base pointer)

三.%ebx 是swap过程中可能会调用的参数

https://www.cnblogs.com/qq78292959/archive/2012/07/20/2600865.html

附上:EAX 是”累加器”(accumulator), 它是很多加法乘法指令的缺省寄存器。

EBX 是”基地址”(base)寄存器, 在内存寻址时存放基地址。

ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。

EDX 则总是被用来放整数除法产生的余数。

但是对于 %eax,%ecx,%edx 不能像ebx一样,保存在callee中

body code

upload successfl

finish code

upload sucu

  1. %ebp-4 就是 old %ebx 的地址 ,把-4(%ebp)移到%ebx,相当于把原来的%ebx复原

2.第二是 把 %ebp 移到 %esp,即现在%esp指向%ebp

(第一第二条指令相当于 pop %ebx)
upload successf

3.popl %ebp
本质上是

1
2
0(%esp),%ebp 
%esp +4

这个时候base pointer复原,变回caller stack 的数值

4.ret 根据返回地址返回

其中leave instruction
等价于
mov %ebp,%esp

popl %ebp

register saving convention

upload succesul

yoo当中,我们希望%edx不会因为调用了who函数之后发生改变,问题是,who调用过程中,%edx是可能发生改变的。

因此我们要制定convention,在使用这些寄存器之前先保存他们!

filename already exists, rnamed

分类如下
fileame already exists, renamed

%eax save the return address so it is caller saved register

example

filename already exis, renamed

upload successf

1
2
3
4
5
6
7
8
9
1.pushl %ebp #save the caller's ebp

2.movl %esp,%ebp #set callee's ebp

3.subl $16,%esp # add 16 bytes, add temporary space

4.movl 8(%ebp),%edx # set edx = x

5.movl $1,-4(%ebp)

filename already eists, renamed

两个pushl 相当于把 s_helper函数的两个参数传了进去

&val始终指向val,当函数结束的时候,返回的值就保存在了-4(%ebp)当中

upload sucessful

Summary

1.若调用函数有多个参数,stack从下到上,依次为第一个,第二个函数。

2.函数调用的参数在stack的上方,就是在caller准备好参数后再调用callee,就是说argument 是caller saved 的

3.函数结果返回在%eax

4.local variable 是存放在 callee-saved registers那里

x86-64 conventions

在x86 中,由于registers 的数目变多,因此可以把local variable 和argument存储在register当中,那样就可以减少对stack 的使用了

下图是16个 8-byte gpr
filename exists, rena

黄色的是caller saved

绿色的是 callee saved

specifications

up

1.用callq 而不用call,因为要返回一个64-bit address,also decrement 8而不是4(8 bytes)

2.因为gpr实现了ebp / rbp 功能,因此不需要使用stack来更新 ,而且rsp能代替实现frame pointer 的功能

3.由于gpr只有6个argument register,所以当函数参数少于六个的时候,不需要用到栈结构

example

upload successfu

q - 8byte - 64bits

l - 4byte - 32bits

w - 2byte - 16bits

b - 1byte - 8bits

filename already exists, rena

prepare for the arguments

1
2
3
4
5
6
7
8
9
movq $1,%rdi
leaq 16(%rsp),%rsi
movl $2,%rdx
leaq 24(%rsp),%rcx
movl $3,%r8
leaq 28(%rsp),%r9
movl $4,(%rsp) #Arg 7
leaq 31(%rsp),%rax
movq %rax,8(%rsp) #Arg 8

upload succesul

1
2
3
4
5
movswl 28(%rsp),%eax # %eax = x3

movsbl 31(%rsp),%edx # %edx = x4

s 表示 signextend

CSAPP-MIPS

Posted on 2018-11-14 | In CSAPP
Registers 寄存器

1.一共有32个general register

2.有两种使用方法: 直接使用对应的编号或者是对应的寄存器名称

寄存器编号 寄存器名称 寄存器用途
0 zero return 0
1 $at 汇编保留寄存器
2-3 $v0-$v1 (value)存储表达式或者是函数的返回值
4-7 $a0-$a3 (Argument) 存储子程序前四个参数,调用时不保存
8-15 $t0-$t7 临时变量,调用时不保存,调用完成后要恢复
16-23 $s0-$s7 函数调用的时候必须保存,调用完成后需要恢复
24-25 $t8-$t9 属性和$t0-$t7一致
26-27 $k0-$k1 (kernel)中断函数返回值
28 $gp global pointer 指向64K大小的静态数据块的中间地址
29 $sp stack pointer
30 $fp/$s8 frame pointer
31 $ra return address

Program Structure 程序结构

本质其实就只是数据声明+普通文本+程序编码(文件后缀为.s,或者.asm也行) 数据声明在代码段之后(其实在其之前也没啥问题,也更符合高级程序设计的习惯)
Data Declarations 数据声明

数据段以 .data为开始标志
声明变量后,即在主存中分配空间。

Code 代码

代码段以 .text为开始标志
其实就是各项指令操作
程序入口为main:标志
程序结束标志(详见下文)
模板
1
2
3
4
5
		.data  # 数据变量声明

.text #代码段

main: 主函数入口

数据声明

example

var1:   .word 3

array:   .byte ‘a’,’b’ # 声明一个存储两个字符的数组array1,并赋值’a’,’b’

array1:   .space 40 #为变量array2 分配40个未使用的连续空间


其他指令汇总

MIPS入门

实战-冒泡排序
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


.data
array: .space 1024
input_msg: .asciiz "Enter the number of integers:\n"
input_int_msg: .asciiz "Enter integers to be sorted\n"
output_msg: .asciiz "the sorted numbers are\n"


.text
.globl main

main:
la $a0,input_msg
li $v0,4
syscall

li $v0,5 #接受用户输入的数组长度
syscall



la $t6,array #数组首地址
move $t7,$zero #循环变量i
move $t8,$zero #循环变量j
move $t9,$v0 #数组长度

addi $t3,$zero,1
beq $v0,$t3,special

input:

la $a0,input_int_msg
li $v0,4
syscall


li $v0,5
syscall

#for(int i=0;i<num;i++)
#cin>>arr[i];

move $t0,$t7 #$t0 is i
sll $t0,$t0,2 #i become byte offset
move $t1,$t6 #$t1 is &array[0]
add $t1,$t1,$t0 #$t1 is &array[i]
sw $v0,0($t1) #cin>>array[i]

addi $t7,$t7,1
blt $t7,$t9,input #if ++i<length

move $t7,$zero

loop1: # 每次外层循环比内层循环的循环变量设为0
move $t8,$zero
loop2:
#read arr[j+1] and arr[j]

move $t2,$t8 #t2 is j
sll $t2,$t2,2 #j=*4;
addu $t1,$t2,$t6 #&arr[j]
lw $t4,0($t1) #arr[j]


addi $t2,$t8,1 #j+1
sll $t2,$t2,2 #(j+1)*4
addu $t0,$t2,$t6 #&arr[j+1]
lw $t5,0($t0) #$t5 = arr[j+1]


bge $t5,$t4,skip #if arr[j+1]>arr[j] skip

sw $t5,0($t1)
sw $t4,0($t0)

skip:


addi $t8,$t8,1 #j++
subi $t1,$t9,1 #t1 = num-1
sub $t1,$t1,$t7 #t1 = num-1-i
blt $t8,$t1,loop2 # if j<num-i-1 t2=1
addi $t7,$t7,1 #i++
sub $t3,$t9,1
blt $t7,$t3,loop1 #if i<num-1 then continue loop1
j output

special:
la $a0,input_int_msg
li $v0,4
syscall


li $v0,5
syscall

#for(int i=0;i<num;i++)
#cin>>arr[i];

move $t0,$t7 #$t0 is i
sll $t0,$t0,2 #i become byte offset
move $t1,$t6 #$t1 is &array[0]
add $t1,$t1,$t0 #$t1 is &array[i]
sw $v0,0($t1) #cin>>array[i]

output:

la $a0,output_msg
li $v0,4
syscall

move $t7,$zero
print:

move $t0,$t7

sll $t0,$t0,2

add $t1,$t6,$t0

lw $a0,0($t1) #argument a0

li $v0,1
syscall

addi $t7,$t7,1
blt $t7,$t9,print

算法-(2018-11-14) Sliding Window&Dijkstra枚举

Posted on 2018-11-14
sliding window leetcode 239

https://leetcode.com/problems/sliding-window-maximum/description/

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
method 1: time complexity O(Nlog(k))

class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {

multiset<int>st;
vector<int>vec;
list<int> lst;
for(int i=0;i<nums.size();i++){
if(i-k>=0){
int top = lst.front();
lst.pop_front();
st.erase(st.find(top));
}

lst.push_back(nums[i]);
st.insert(nums[i]);
if(i-k>=-1){
vec.push_back(*st.rbegin());
}

}

return vec;

}
};


method 2

1. use a deque to store the index in the window , each window must fall in the range(i-k+1,i). so whenever an index smaller than i-k+1,pop_front() to discard the element;

2. when insert a new element nums[i], compare it with the top of the queue , until q[front]>nums[i] ,that is to say, q[front] is always the max element in the window

vector<int> maxSlidingWindow(vector<int>& nums, int k) {

deque<int>dq;
vector<int>res;
if(nums.empty())
return res;
for(int i=0;i<nums.size();i++){
while(!dq.empty()&&dq.front()<i-k+1){
dq.pop_front();
}
//remove smaller elements in window
while(!dq.empty()&&nums[dq.back()]<=nums[i]){
dq.pop_back();
}
dq.push_back(i);
if(i-k+1>=0){
res.push_back(nums[dq.front()]);
}
}
return res;

}

这里sliding window就是移动窗口的时候,每次把最左边的值pop掉,然后不断比较最右边的值与新插入的值,保证新插入的值是最好的candidate,那么窗口最左边的值就会是最大值。

Kattis

#include

#include

#include

#include

#include

#include

#include

using namespace std;

struct edge{
int from;
int to;
int weight;
};

typedef pair<int,int> ip;

#define INF 0x3f3f3f3f

void dijkstra(vector<vector>&adj,vector&dist,int source){

priority_queue<ip,vector<ip>,greater<ip>> q;
q.push({0,source});
dist[source] = 0;
while(!q.empty()){
    auto top = q.top();
    q.pop();
    int u = top.second;
    for(auto p:adj[u]){
        int v = p.first;
        int weight = p.second;
        if(dist[v]==INF||dist[v]>dist[u]+weight){
            dist[v] = dist[u]+weight;
            q.push(make_pair(dist[v],v));
        }
    }
}

}

https://open.kattis.com/problems/flowerytrails

对于有多条最短路,并且求所有最短路的路径之和

1.分别对起始点和终点跑DIjkstra

2.枚举每一条初始点与终点分别到这条边距离,分别为d1,d2,如果d1+d2+w==shortestPath,则ans+=w;

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
int main(){

int P,T;
cin>>P>>T;
vector<vector<ip>> adj(P);
vector<vector<ip>> parent(P);
vector<int>distStart(P,INF);
vector<int>distEnd(P,INF);
vector<tuple<int,int,int>>edgeVec;

for(int i=0;i<T;i++){
int from,to,w;
cin>>from>>to>>w;
edgeVec.push_back(make_tuple(from,to,w));
adj[from].push_back(make_pair(to,w));
adj[to].push_back(make_pair(from,w));
}

dijkstra(adj,distStart,0);
dijkstra(adj,distEnd,P-1);

int shortestPath = distStart[P-1];
int ans=0;
for(int i=0;i<T;i++){
int from = get<0>(edgeVec[i]);
int to = get<1>(edgeVec[i]);
int weight = get<2>(edgeVec[i]);

if(distStart[to]+distEnd[from]+weight==shortestPath||
distStart[from]+distEnd[to]+weight==shortestPath){
ans+=weight;
}

}

cout<<ans*2;

return 0;
}
1…3456
Alex Chiu

Alex Chiu

Alex's personal blog

54 posts
4 categories
4 tags
© 2019 Alex Chiu
Powered by Hexo
|
Theme — NexT.Mist v5.1.4