標籤

顯示具有 link list 標籤的文章。 顯示所有文章
顯示具有 link list 標籤的文章。 顯示所有文章

2013年9月10日 星期二

link list-小專題-記憶體管理-free

// move the nth node out from occupied list
MemInfo *move_out(int n)
{
   MemInfo *rush = head_occupy;
   // get the nth node
   while (n != 0) {
      rush = rush->next;
      n--;
   }
   // prev->rush->next
   // prev->next
   if (rush->next != NULL)
      rush->next->prev = rush->prev;
   if (rush == head_occupy)
      head_occupy = rush->next;
   else
      rush->prev->next = rush->next;
   return rush;
}

// move node to free list
void free_(MemInfo *node)
{
   MemInfo *rush, *prev;
   // initialize rush as head
   rush = head_free;
   // find out the position where node should
   // be put in
   while (rush->begin < node->begin) {
      prev = rush;
      rush = rush->next;
   }
   // if this node should be put in the first place
   if (rush == head_free) {
      // if rush is right after node
      if (rush->begin == node->begin + node->size) {
         // then merge them together
         rush->begin = node->begin;
         rush->size += node->size;
      }
      // otherwise
      else {
         node->next = rush;
         node->prev = NULL;
         rush->prev = node;
         head_free = node;
      }
   }
   // if node is right after prev
   else if (prev->begin + prev->size == node->begin) {
      // merge them together
      prev->size += node->size;
      // if they are connected together
      if (prev->begin + prev->size == rush->begin) {
         prev->size += rush->size;
         prev->next = rush->next;
         if (rush->next != NULL)
            rush->next->prev = prev;
      }
   }
   // else, connect them
   else {
      node->next = rush;
      node->prev = prev;
      rush->prev = node;
      prev->next = node;
   }

}
free_()這個function是要free掉一個已經使用的node。因此為了示範,我們必須從occupied的list中拿出一個node出來free掉。

因此這邊我們就寫了一個function:move_out(),它將會從occupied list中移出第n個node出來。

我們declare一個pointer rush讓它從head_occupy開始去找第n個node,然後用while迴圈去判斷是否到達第n個node:
   MemInfo *rush = head_occupy;
   // get the nth node
   while (n != 0) {
      rush = rush->next;
      n--;
   }
因此這邊的rush就會是第n個node,當然最好的判斷式應當是:
while(rush != NULL &&n != 0) {
   rush = rush->next;
   n--;
}
if (n != 0) {
   printf("occupied list doesn't has %d elements!\n", n);
   return;
}
否則如果這個occupied list根本就不足n個element的時候,rush將不會是well defined的指標,它將會跑到NULL去,接著rush->next將會不存在。

接著稍微注意尾端與前端之後,就該把rush這個node從中移除掉:
   if (rush->next != NULL)
      rush->next->prev = rush->prev;
   if (rush == head_occupy)
      head_occupy = rush->next;
   else
      rush->prev->next = rush->next;
最後return rush便完成了我們要的move_out的function,也就是初步的free就應當先把node移出去。
接著這個移出去的node將會放到free的list上。但是要放在哪裡就必須取決於它的位置begin。因此在free_()中我們需要一個迴圈,為了找出適當的位置:
   while (rush->begin < node->begin) {
      prev = rush;
      rush = rush->next;
   }
如果有找到適當的位置,那麼rush將會是下一個節點,同時prev將會是上一個節點。

接著我們開始判斷位置對應關係,首先如果這個node的begin於free的list中是最小的,那麼它該置放在head的位置:
   if (rush == head_free) {
      // if rush is right after node
      if (rush->begin == node->begin + node->size) {
         // then merge them together
         rush->begin = node->begin;
         rush->size += node->size;
      }
      // otherwise
      else {
         node->next = rush;
         node->prev = NULL;
         rush->prev = node;
         head_free = node;
      }
   }
這時候我們必須要注意的一點就是,如果rush正好記憶體的位置是接在node的後端的話,那麼我們就直接將這兩個node整併成同一個node:
      if (rush->begin == node->begin + node->size) {
         // then merge them together
         rush->begin = node->begin;
         rush->size += node->size;
      }
而如果中間有斷層的話,就不用這樣處理,就直接linked list接上去就可以了:
      else {
         node->next = rush;
         node->prev = NULL;
         rush->prev = node;
         head_free = node;
      }
要注意的是head的前端是NULL,而這邊由於NODE是最小的位置因此他將會是head。

接下來:
   else if (prev->begin + prev->size == node->begin) {
      // merge them together
      prev->size += node->size;
      // if they are connected together
      if (prev->begin + prev->size == rush->begin) {
         prev->size += rush->size;
         prev->next = rush->next;
         if (rush->next != NULL)
            rush->next->prev = prev;
      }
   }
如果他並非擺在head的位置,但是它正巧在記憶體中的位置是在prev的後方接密緊鄰,那麼它應當要跟prev併為同一個node:
      prev->size += node->size;
同時要注意的是,如果這時後面那一個node如果正好也緊密接鄰的話,同樣我們把後面的node與前方已併完的node再次併一次:
      if (prev->begin + prev->size == rush->begin) {
         prev->size += rush->size;
         prev->next = rush->next;
         if (rush->next != NULL)
            rush->next->prev = prev;
      }
這時後要注意的是prev的next指向的地方需要改正。那麼為什麼上一次處裡不需要改正prev->next = node->next呢? 因為node並沒有next,而且prev的next並不需要改。最後的if就是判斷是否為尾端,如果不是尾端我們再行處理,否則NULL的prev是沒有定義的。

最後,如果並沒有併攏情形,那麼就正常處理,前面與node結合,後面與node結合:
   else {
      node->next = rush;
      node->prev = prev;
      rush->prev = node;
      prev->next = node;
   }

2013年9月4日 星期三

link list-小專題-記憶體管理-增加occupy list

// add node to occupy list
void add_to_occupy_list(MemInfo *node)
{
   // set this node to head of the occupy list
   node->prev = NULL;
   if (head_occupy != NULL)
      head_occupy->prev = node;
   node->next = head_occupy;
   head_occupy = node;

}
我們除了要記錄可用的list以外,也同時必須記錄已經使用的list才有意義。因此不管是從first fit又或是best fit return出來的node是已經使用中了,那麼就必須要把這個node加到head_occupy上。這邊的function就是在做這件事情。

而為了節省速度,使用了有點像是先進後出的概念去做這一條head_occupy的list,也就是head_occupy always是指向最新使用的node,當然要節省速度也不是只有這種方法,我們也可以使用tail_occupy去指向這個list的tail,而由於是雙向的list,因此可以使用prev指標便能輕易的移動。

我們用了先進後出,所以這個node將會是head_occupy,因此它的prev將會是NULL,於是便先指定:
   node->prev = NULL;
接著把目前的head_occupy往前接給現在的node,但同時必須先注意head_occupy是否是空指標,因為如果是空指標是沒有定義記憶體空間的,因此它的prev便沒有合法定義:
   if (head_occupy != NULL)
      head_occupy->prev = node;
然後把現在的node往後接head,邏輯同前面的敘述,但我們就直接接head_occupy便可,因為即使head_occupy是空指標,目前的node往後接空指標是我們要的狀況:
  node->next = head_occupy;
最後重新指定head_occupy就大功告成了。

2013年9月3日 星期二

link list-小專題-記憶體管理-best fit allocation

// allocate a memory in a linked list by best fit
MemInfo *best_fit_mem_alloc(unsigned size)
{
   MemInfo *best_fit = NULL, *data, *rush;
   // record the smallest difference
   int smallest_diff = INT_MAX;
   // assigned this from head node for un-used memory
   rush = head_free;
   // find out the best fit
   while (rush != NULL) {
      if (rush->size > size &&
                (rush->size - size) < smallest_diff) {
         smallest_diff = rush->size - size;
         best_fit = rush;
      }
      rush = rush->next;
   }
   // if we could find out the best fit
   if (best_fit != NULL) {
      // allocate a memory
      data = (MemInfo*) malloc(sizeof(MemInfo));
      data->begin = best_fit->begin;
      data->size = size;
      if (best_fit->size > size) {
         best_fit->begin += size;
         best_fit->size -= size;
      }
      else {
         // jusdge if this is head
         if (best_fit == head_free)
            head_free = best_fit->next;
         else
            best_fit->prev->next = best_fit->next;
         // if this is not tail
         if (best_fit->next != NULL)
            best_fit->next->prev = best_fit->prev;
         free(best_fit);
      }
   }
   return (data);
}
基本上best fit和first fit整體的程式邏輯是相同的,唯一不同的地方就是前面要尋找出最適合大小的size的node而已。因此這邊就導入了一個pointer: rush專門用來當成是迴圈從head跑到最後。唯有這種方式才能找到最適合大小的size。

因此我們修改下while的條件為rush != NULL表示rush要從頭跑到尾,然後best_fit的指標就是專門記載"目前最適合的node"的位置,我們並添加了一個smallest_diff就是專門記載"目前最適合的node"的size和我們需要大小的差異。差異越小的越是滿足最適合大小的要件:
   while (rush != NULL) {
      if (rush->size > size &&
                (rush->size - size) < smallest_diff) {
         smallest_diff = best_fit->size - size;
         best_fit = rush;
      }
      rush = rush->next;
   }
所以我們這邊就多添加了一個if就是專門記載best_fit以及smallest_diff,記錄最適合大小的pointer以及size差異值。如果目前的node: rush的size比我們要allocate的size還大,並且size的差異呢,還比上一筆best_fit的差異還小的話,那麼它就會比上一筆best_fit還更適合當最適合大小的node。這邊的邏輯便然是如此。

接下來的程式就會和first fit是一模一樣的做法,詳情就請參考上一篇的first fit allocation。

2013年8月30日 星期五

link list-小專題-記憶體管理-first fit allocation

// node structure for memory info
typedef struct MemInfo {
   unsigned begin, size;
   struct MemInfo *prev, *next;
}MemInfo;

// head node for occupied memory linked list
MemInfo *head_occupy = NULL;
// head node for un-used memory linked list
MemInfo *head_free = NULL;

MemInfo *tail_free = NULL;
// insert data to tail of the linked list
void Mem_info_insert_in_tail(unsigned begin, unsigned size)
{
   // create a new node for this data
   MemInfo *data = (MemInfo*) malloc(sizeof(MemInfo));
   data->begin = begin;
   data->size = size;

   // if there is no existing data
   if (head_free == NULL)
      head_free = tail_free = data;
   else {
      tail_free->next = data;
      data->prev = tail_free;
      data->next = NULL;
      tail_free = data;
   }
}

void Mem_info_show_list(void)
{
   MemInfo *rush = head_free;
   printf("The result is:\n");
   while (rush != NULL) {
      printf("(%d, %d) ", rush->begin, rush->size);
      rush = rush->next;
   }
   puts("");
}
// allocate a memory in a linked list by first fit
MemInfo *first_fit_mem_alloc(unsigned size)
{
   MemInfo *first_fit, *data = NULL;
   // assign this from head node for un-used memory
   first_fit = head_free;
   // find out first_fit node
   while (first_fit != NULL && first_fit->size < size)
      first_fit = first_fit->next;
   // if we could find out one
   if (first_fit != NULL) {
      // allocate memory for this data
      data = (MemInfo*) malloc(sizeof(MemInfo));
      data->begin = first_fit->begin;
      data->size = size;
      // if the size of this first fit is larger than
      // allocation, then calculate the new size
      if (first_fit->size > size) {
         first_fit->begin += size;
         first_fit->size -= size;
      }
      else {
         // judge if this is head
         if (first_fit == head_free)
            head_free = first_fit->next;
         else
            first_fit->prev->next = first_fit->next;
         // if this is not tail
         if (first_fit->next != NULL)
            first_fit->next->prev = first_fit->prev;
         free(first_fit);
      }
   } // if (first_fit != NULL)
   return data;
}

int main()
{
   unsigned begin, size;
   printf("Mem info linked list:\n");
   fin = fopen("mem_info_input", "r");
   while (fscanf(fin, "%d %d", &begin, &size) != EOF) {
      printf("(%d, %d) ", begin, size);
      Mem_info_insert_in_tail(begin, size);
   }
   Mem_info_show_list();
   first_fit_mem_alloc(1000);
   Mem_info_show_list();
   fclose(fin);

   return 0;

}

記憶體的管理,是我們在目前幾乎所有的os都會碰到的課題。因為都是多工的,也就是許多的job都會切成一小段一小段分別/管線/平行的去執行。而多個程式也會因此同時在記憶體之中allocate。但因為切分的關係,每支程式所需要的記憶體都不盡然相同。有的要42K,有的要32M,有的只需小小的0.1K。而正常的程式在執行結束之後,都會free掉記憶體。因此常常會出現中間部分的記憶體是已經free掉了,因此我們需要記錄那塊free掉的記憶體的位置以及資料,這樣才能夠利用。在這使用了linked list來當作實做的工具。這個list會有四筆資料在內。我們用了雙向的linked list於是有兩筆分別指向前(prev)與後(next)的node。然後我們還需要這個node的記憶體起始位置(begin)以及大小(size):
typedef struct MemInfo {
   unsigned begin, size;
   struct MemInfo *prev, *next;
}MemInfo;

而我們也使用了兩個串列,一個串列head_free是為了記錄有哪些記憶體是空的,可以使用的。那麼相反的,head_occupy就會是記錄目前使用中的記憶體的串列。而一樣linked list為了方便起見我們也記錄了尾端tail_free

為了實做allocation,一樣我們還是需要資料輸入工具,因此邏輯上相同於普通的linked list中的insert_in_tail(),在這邊我們唯一不同的就是輸入兩筆記錄begin和size,因此Mem_info_insert_in_tail()基本上就和insert_in_tail()是一樣的東西,不多加贅述。

而這邊的主軸,就是要在free的串列之中找到第一個比我們要求的size還要大的記憶體node。因此邏輯上是很簡單的,只要從head_free開始去比較大小,然後找到之後在去對這個node處理即可。

首先我們需要一個pointer去四處搜尋我們要的size,我為它取名為first_fit,也就是它的目的是為了尋找出第一個適合的大小的node,而這個function也需要回傳一個node是要標示我們為他allocation的node,取名為data:
   MemInfo *first_fit, *data;
接著我們便去找出適合的大小:
   while (first_fit != NULL &&first_fit->size < size)
      first_fit = first_fit->next;
這時候找到之後,first_fit就會是我們要的node(另一個情形就是並沒有找到我們要的大小,因此這邊的first_fit就會是NULL),因此當我們有找到的狀況下才需要做allocation和後續的處理:
   if (first_fit != NULL) {
      // allocate memory for this data
      data = (MemInfo*) malloc(sizeof(MemInfo));
      data->begin = first_fit->begin;
      data->size = size;
而所謂的後續的處理呢,有兩種狀況,第一種就是我們尋找到的node的size比我們需求的大,這時候處理方式就會是把我們找到的node的起始位置begin往下移開size大小(因為我們要在這個node的記憶體allocate size大小,所以free的起始位置就會少掉了size大小),同時這個node的大小便同時會減少了size這麼大:
      if (first_fit->size > size) {
         first_fit->begin += size;
         first_fit->size -= size;
      }
另一種狀況就是我們找到的node的大小正巧符合我們要的大小,但我們需要特別注意的就是如果我們找到的node是第一個,也就是head_free,這時候我們需要重新指定head_free為下一個node(第一種狀況不用,因為head_free比size大,並沒有把整個node移掉,不需重新指定)。接下來就是把前面那個node往下一個node接,下一個node往前接,最後在free掉即可:
      else {
         // judge if this is head
         if (first_fit == head_free)
            head_free = first_fit->next;
         else
            first_fit->prev->next = first_fit->next;
         // if this is not tail
         if (first_fit->next != NULL)
            first_fit->next->prev = first_fit->prev;
         free(first_fit);
      }
最後回傳我們已經allocated的記憶體就大功告成:
  return data

2013年8月23日 星期五

link list-double link list的刪除某值

typedef struct Dnode {
   struct Dnode *prev;
   int val;
   struct Dnode *next;
}Dnode;

Dnode *Dhead = NULL;

// show doule linked list elements
void Dshow_list(void)
{
   Dnode *rush = Dhead;
   printf("\n The result is:\n");
   while (rush != NULL) {
      printf("%d ", rush->val);
      rush = rush->next;
   }
   puts("");
}

// delete a specified value from a double linked list
void Ddelete_specified_data(int val)
{
   Dnode *rush, *prev;
   rush = Dhead;
   // find out the value
   while (rush->val != val) {
      prev = rush;
      rush = rush->next;
   }
   // if there is no found or no data in the linked list
   if (rush == NULL) {
      printf("No found!/ No existing data!\n");
      return;
   }
   // now rush is the node which has the value
   // then delete rush
   // if 1st data is rush
   if (rush == Dhead) {
      rush->next->prev = NULL;
      Dhead = rush->next;
   }
   // or if rush is the tail
   else if (rush->next == NULL) {
      rush->prev->next = NULL;
      Dtail =rush->prev;
   }
   // else
   else {
      rush->prev->next = rush->next;
      rush->next->prev = rush->prev;
   }

   free(rush);
}

Dnode *Dtail = NULL;
// insert data to tail of the linked list
void Dinsert_in_tail(int val)
{
   // allocate memory for this new node
   Dnode *data = (Dnode*) malloc(sizeof(Dnode));
   data->val = val;
   data->next = NULL;

   // if there is no existing nodes in the list
   if (Dhead == NULL) {
      // then head = tail = node
      Dhead = Dtail = data;
      data->prev = NULL;
   }
   else {
      Dtail->next = data;
      data->prev = Dtail;
      Dtail = data;
   }

}

int main()
{
   int n;
   printf("Double linked list:\n");
   FILE *fin;
   fin = fopen("input", "r");
   while (fscanf(fin, "%d", &n) != EOF) {
      printf("%d ", n);
      Dinsert_in_tail(n);
   }
   Dshow_list();
   Ddelete_specified_data(5);
   Dshow_list();

   return 0;

}

單向的(也就是基本型態)linked list有個壞處就是不能反向移動。當然使用環狀的結構是可以繼續往下走然後找到自己想要的element,但如果資料過長,而你想要找的資料剛好是目前的正上方,這時候使用環狀結構反而是浪費了一堆時間。因此我們便多使用了一個儲存空間,使用了指標去指向目前node的上一個。
typedef struct Dnode {
   struct Dnode *prev;
   int val;
   struct Dnode *next;
}Dnode;
這樣便能夠反向搜尋我們所要的東西,不過缺點就是每個node都會增加一筆資料空間。而為了示範刪除某值,也必須先建立基本配備。首先我們使用了Dinsert_in_tail()的function,當然這個東西跟我們一般單向的insert_in_tail()邏輯上是大同小異,不再多做介紹,唯一不同之處在於head之前接的會是NULL,因為我們需要一個指標是用來標示尾端。而因為這是雙向的,所以頭也必須要和尾一樣接的是NULL,類似接地的功能。而Dshow_list()也是和show_list()是同樣的function,在此就不贅述了。

雙向linked list在刪除某值的時候,要注意的是我們要處理的工夫會多雙倍。原本的單向只需要把前一個node的next指向下一個node就可以刪除了。但在這邊,我們必須要注意很多地方。

首先是如果我們要刪除的資料是第一筆的話,也就是head,那麼head的前一個node是空指標。刪除掉head的時候必須把下一個node的prev指向空指標:
   if (rush == Dhead) {
      rush->next->prev = NULL;
      Dhead = rush->next;
   }
然後再重新指定head即可。

至於tail也會是類似的處理:
   else if (rush->next == NULL) {
      rush->prev->next = NULL;
      Dtail =rush->prev;
   }
而最後是一般情形,也就是想刪除的資料不是頭也不是尾而是中間。處理方法就是:
(1) 前一個node的next指向下一個node
(2) 下一個node的prev指向前一個node
要離開之前總得要把事情交辦的清清楚楚,前後所指向的指標都得有所依據這樣才不會指向沒有定義的記憶體區塊:
      rush->prev->next = rush->next;
      rush->next->prev = rush->prev;

2013年8月21日 星期三

link list-reverse


// reverse a link list
void reverse_link_list(void)
{
   // we need 3 pointers to keep going
   node *rush, *prev, *now;

   // initialize
   rush = head;
   now = NULL;

   // loop from head to final
   while (rush != NULL) {
      // prev now rush
      prev = now;
      now = rush;
      rush = rush->next;
      // prev<-now rush
      now->next =prev;
   }
   // assign head to now
   head = now;
}

反轉一個link list我們會需要三個指標。因為如果照以前一樣只用兩個指標來做的話:
while (rush != NULL) {
   prev = rush;
   rush = rush->next;
   rush->next = prev;
}
我們會永遠沒辦法往下一個node前進,因為現有的now的next已經往前接了,而該往後的node會因此斷了音訊。所以我們需要一個pointer來保存下一個node的位置。因此rush是用來保存下一個node的位置,而now是現有的node,prev則是前一個node。由於我們要反轉一個link list,所以now的next應當接在prev上。

我們初始化指標使得: now(NULL) rush(head)

這樣在進入迴圈做這件事情的時候:
      prev = now;
      now = rush;
      rush = rush->next;
      // prev<-now rush
      now->next =prev;
就會讓前頭長成這樣 prev(NULL)<-now(head) rush(next)

這樣子我們就把目前的head接給NULL,也因為我們要反轉的關係,head將來就會成為最後一個節點,而我們知道最後一個節點的next是NULL,因此這樣做是沒問題的,這也是支所以我們把now initialize為NULL的初衷。

與使用兩個指標的方式不同的是,由於我們有保存下一個指標的位址rush,所以我們要往下一個節點邁進的時候,只需要把rush assign給now。然後rush就可以往下一個節點邁進。

接著最後再把head assign給目前的now(而不是rush!因為經過迴圈之後rush已經是NULL了):
   head = now
大功告成!

2013年8月19日 星期一

link list-清除link list

// free all nodes in the link list
void free_all_nodes(void)
{
   node *rush, *prev;
   // if there is no existing element, jump out
   if (head == NULL) {
      printf("No existing element!\n");
      exit(1);
   }
   // initialize rush as head
   rush = head;
   // free all elements
   while (rush != NULL) {
      prev = rush;
      rush = rush->next;
      free(prev);
   }
}
當我們allocate一個link list的時候,記憶體會騰出空間出來。當然C的程式的default會在程式結束之後把記憶體清空。但是如果你有程式是巨量資料,那麼這list要清空騰出記憶體是無可避免的。

因此要清空一個list的記憶體的方式就是從頭慢慢的一個一個開始清。他不像是一般array使用free就可以清除一整筆資料。由於目前的link list是單向的,所以清除順序勢必是要從head開始清除。

所以首先我們需要先判斷link list裡面究竟有沒有existing的node再來決定要不要清除。因為如果沒有的話便會清除到NULL指標。當然也許程式可能是容許的,但是如果可以養成程式能夠自我預警,那麼日後出現任何問題都能夠容易debug,也能夠有點像是封裝概念一般省去一堆debug的時間。
   if (head == NULL) {
      printf("No existing element!\n");
      exit(1);
   }
這邊就是偵測目前是否有現存資料在內,如果沒有那麼就印出訊息跳出去。當然如果你整支程式還是要繼續執行的話就把exit(1)改成return即可。

接著initialize rush為head之後開始迴圈一個一個開始free掉每個node:
   rush = head;
   // free all elements
   while (rush != NULL) {
      prev = rush;
      rush = rush->next;
      free(prev);
   }
while迴圈就偵測目前是否有到尾了,如果還沒到尾就繼續free掉現有值。直到迴圈結束這支程式也做了該做的事情了。

2013年8月18日 星期日

link list-刪除某值之資料

// delete a node of data val in a link list
void delete_specified_data(int val)
{
   node *rush, *prev;
   rush = head;
   // search for data val
   while (rush != NULL && val != rush->val) {
      prev = rush;
      rush = rush->next;
   }
   // if there is no result
   if (rush == NULL) {
      printf("Data no found!\n");
      exit(1);
   }
   // if that result is the first node,
   // then head will be the next node
   else if (rush == head)
      head = rush->next;
   // else, pull out the node
   else
      prev->next = rush->next;
   free(rush);

}
這個是只刪除某一個值,但是只刪除一個而已,因此如果一個數列23 54 54 34 65 12 在這邊如果刪除54的話,只會刪除第一個54。 所以概念上就是從link list之中從頭開始找,找到符合值為val的就把它刪除,刪除方式就是把他前面的node的next接它後面。接著再free掉它。
   rush = head;
   // search for data val
   while (rush != NULL && val != rush->val) {
      prev = rush;
      rush = rush->next;
   }
首先把探索用的指標rush initialize為head,接著迴圈內就開始找第一個符合我們要找的值的node為止。這時候rush有幾種情形
1. 第一個node就找到了
2. 沒有找到
3. 找到了,但不是1.的情形

while內的條件最好是該有正確的boundary判斷,因此rush != NULL,因為筆者的參考書沒有這一段,所以特別標明這一點。如果這個link list沒有找到我們要找的資料的話,那麼最後rush會跑到NULL上,接著會讀取不存在的記憶體rush = rush->next,這樣是有問題的。

所以當我們找到的是第1.的情形的時候,他會和第三個有所不同。因為第三個情形的處理方式就是該把(我們找到的node)前面那個的next給接到(我們找到的node)的後一個。但如果是第一個情形不會有前面那個node,這時候prev是沒有Initialize的所以一樣會讀取到沒有allocate的記憶體上。
   else if (rush == head)
      head = rush->next;
   // else, pull out the node
   else
      prev->next = rush->next;
所以第一個else if就是判斷第一個情形,這時候因為我們要移掉的是第一個node,處理方式就是重新指定head為後一個node就行了。而第三種情形的處理方式就是把前面那個node往後接給後面那一個node。

至於第二個情形
   if (rush == NULL) {
      printf("Data no found!\n");
      exit(1);
   }
就該把沒找到的訊息印出來並且直接跳出去。筆者的參考書一樣沒這一段,雖然本身無關緊要。因為如果沒有找到的話,那麼我們最後要free()的時候便會free掉什麼都沒有的NULL,這本身是沒什麼問題的。但寫程式概念還是要清楚,畢竟free是用在有memory allocate的情形的。萬一某個部分沒有寫好,比如我們的while如果沒有判斷rush != NULL,這時候會發生什麼事情也不曉得。即使free掉NULL沒什麼要緊的,但寫給自己看也讓自己提醒自己該注意的地方是什麼才是重要的。

2013年8月16日 星期五

link-list 資料插尾插頭

// insert val in head of a link list
void insert_in_head(int val)
{
   // create a node for val
   node *data = (node *) malloc(sizeof(node));
   data->val = val;
   // link head next to data
   data->next = head;
   // new head = data
   head = data;
}
node *tail = NULL;
// insert val in tail of a link list
void insert_in_tail(int val)
{
   // create a node for val
   node *data = (node *) malloc(sizeof(node));
   data->val = val;
   // link NULL to data
   data->next = NULL;

   // if there is no existing node
   if (head == NULL)
      // then this node is head and also tail
      head = tail = data;
   else {
      // otherwise, this node is new tail
      tail->next = data;
      tail = data;
   }
}
資料的插頭,就是把一筆資料直接放入到link list的頭端,如果沒有資料那麼這筆資料就會是新的頭。如果有資料的話那麼目前的頭就會被接在這筆新資料之後。
   data->next = head;
   // new head = data
   head = data;
在create一個node之後我們便把目前的頭接在這筆新資料之後,然後新的頭就會是這筆新資料。在此,即使是沒有任何資料的狀態也不會有問題,因為沒有任何資料的狀況之下,我們早在一開始便把head initialize成為NULL了,因此這時候這筆新資料往後接的,便會是NULL,這也是正常該有的狀況。

而資料的插尾,就是把這筆資料直接放到最尾端。我們可以直接一個一個從頭去search next指標直到尾端去找尾端然後把這筆資料往後接上,但這太耗費工夫,尤其如果是巨量資料之下的話會更明顯。因此我們直接create一個指標tail,指向這個list的最後一個node,我們一開始initialize為NULL:
node *tail = NULL;
接著這個程式create memory並放入資料給這筆新資料之後,首先就將這筆新資料的尾端接為NULL:
   data->next = NULL;
然後我們先判定目前是不是現有資料,原因在於如果不管三七二十一不判定有沒有資料而直接把tail往後接給這筆資料的話,會有問題,因為我們並沒有配給tail一個記憶體空間,所以它的next是不存在的,因為即使是指標,還是需要記憶體空間的。所以如果沒有任何資料在list之內,我們直接把tail和head assign給這筆新資料:
   if (head == NULL)
      // then this node is head and also tail
      head = tail = data;
當然我們也可以用tail == NULL來判斷也是一樣的結果。邏輯上來講,如果沒有任何資料存在,那麼這筆新資料就會是這個link list的頭兼尾,這邊程式的邏輯就會把這段話給烙印上去。

接著如果現有資料的話,我們就把tail往後接這筆資料,接著才把tail assign給這筆資料,這邊順序要對,如果我們先把tail assign給這筆新資料在把tail往後接給這筆資料的話,結果會是這筆資料是孤立的,沒有任何資料接它,並且它的next會是接它自己。
      tail->next = data;
      tail = data;
於是接頭接尾便能夠很簡單的用幾行就可以完成。

2013年8月14日 星期三

link list-資料的依值插入

typedef struct node {
   int val;
   struct node *next;
}node;

node *head = NULL;
// insert value "val" into link list
void insert_value(int val)
{
   node *prev, *rush;
   // create node for that insertor
   node *data = (node*) malloc(sizeof(node));
   // put the value in that node
   data->val = val;

   prev = rush = head;
   // find out the correct position for val
   while (rush != NULL && val > rush->val) {
      prev = rush;
      rush = rush->next;
   }

   // link it next to the bigger value
   data->next = rush;
   // if there is no element in link list
   // or val is the smallest
   if (rush == head)
      head = data;
   else
      prev->next = data;

}

// show link list elements
void show_list(void)
{
   node *rush = head;
   printf("\nThe result is:\n");
   while (rush != NULL) {
      printf("%d ", rush->val);
      rush = rush->next;
   }
   puts("");


}
int main()
{
   int n;
   printf("Enter values:\n");
   while(scanf("%d", &n) != EOF) {
      printf("%d ", n);
      insert_value(n);
   }
   show_list();
   return 0;

}

關於include的部分請依自己的compiler把該include的東西放進去。

依值插入顧名思義就是依據值的大小而插入適當的位置,因此不管目前插入的值是什麼,整個list都會是由小排到大(又或是由大排到小)。

所以這個function邏輯是
1. 找到相等又或是比插入值還大的node
2. 把插入值的node next 接在1.前面
3. 把比插入值還小的node接在插入值前面,如果是NULL node又或是插入值是最小的,那麼插入值它就會是head

因此指標prev和rush就是在step 1中為了找出比插入值還小的prev的node,以及該接在插入值後面的rush


   while (rush != NULL && val > rush->val) {
      prev = rush;
      rush = rush->next;
   }
在這個while迴圈中,不斷的比較插入值和現有在list中node的大小。當現有的node還不到尾端的時候,且目前還沒找到該插入的位置的時候,在list中的node就會往後移,直到rush這個指標比插入值還大或相等,那麼理所當然的prev就是比插入值還小。

從while迴圈跳出來會有兩種情形 a.list是空的 b.list非空的且有找到插入值該有的位置,因此以b.來說,我們依循以上function的邏輯2. 我們就會把插入值的next往後接rush:

   // link it next to the bigger value
   data->next = rush;
但如果是情形a呢?這也沒問題,因為即使是空的,我們一開始initialize head是NULL的,而link list的尾端總是NULL的,所以目前的rush也會是NULL的。所以把插入值的尾端這樣接是不會有問題的。

所以邏輯3.:
   if (rush == head)
      head = data;
   else
      prev->next = data;

就會和描述一模一樣,如果插入值該放第一個又或是這個list是空的,那麼沒有一個node該接他前面,所以head就會assign給他。如果不是的話,那麼前面那個prev就會是比她還小的值,應當接在插入值的前方。