練習42:棧和佇列
原文:Exercise 42: Stacks and Queues
譯者:飛龍
到現在為止,你已經知道了大多數用於構建其它資料結構的資料結構。如果你擁有一些List
、DArray
、Hashmap
和 Tree
,你就能用他們構造出大多數其它的任何結構。你碰到的其它任何結構要麼可以用它們實現,要麼是它們的變體。如果不是的話,它可能是外來的資料結構,你可能不需要它。
Stack
和Queue
是非常簡單的資料結構,它們是List
的變體。它們是List
的弱化或者轉換形式,因為你只需要在List
的一端放置元素。對於Stack
,你只能能夠在一段壓入和彈出元素。而對於Queue
,你只能夠在開頭壓入元素,並在末尾彈出(或者反過來)。
我能夠只通過C前處理器和兩個標頭檔案來實現這兩個資料結構。我的標頭檔案只有21行的長度,並且實現了所有Stack
和Queue
的操作,不帶有任何神奇的定義。
我將會向你展示單元測試,你需要實現標頭檔案來讓它們正常工作。你不能建立stack.c
或 queue.c
實現檔案來通過測試,只能使用stack.h
和 queue.h
來使測試執行。
#include "minunit.h"
#include <lcthw/stack.h>
#include <assert.h>
static Stack *stack = NULL;
char *tests[] = {"test1 data", "test2 data", "test3 data"};
#define NUM_TESTS 3
char *test_create()
{
stack = Stack_create();
mu_assert(stack != NULL, "Failed to create stack.");
return NULL;
}
char *test_destroy()
{
mu_assert(stack != NULL, "Failed to make stack #2");
Stack_destroy(stack);
return NULL;
}
char *test_push_pop()
{
int i = 0;
for(i = 0; i < NUM_TESTS; i++) {
Stack_push(stack, tests[i]);
mu_assert(Stack_peek(stack) == tests[i], "Wrong next value.");
}
mu_assert(Stack_count(stack) == NUM_TESTS, "Wrong count on push.");
STACK_FOREACH(stack, cur) {
debug("VAL: %s", (char *)cur->value);
}
for(i = NUM_TESTS - 1; i >= 0; i--) {
char *val = Stack_pop(stack);
mu_assert(val == tests[i], "Wrong value on pop.");
}
mu_assert(Stack_count(stack) == 0, "Wrong count after pop.");
return NULL;
}
char *all_tests() {
mu_suite_start();
mu_run_test(test_create);
mu_run_test(test_push_pop);
mu_run_test(test_destroy);
return NULL;
}
RUN_TESTS(all_tests);
之後是queue_tests.c
,幾乎以相同的方式來使用Queue
:
#include "minunit.h"
#include <lcthw/queue.h>
#include <assert.h>
static Queue *queue = NULL;
char *tests[] = {"test1 data", "test2 data", "test3 data"};
#define NUM_TESTS 3
char *test_create()
{
queue = Queue_create();
mu_assert(queue != NULL, "Failed to create queue.");
return NULL;
}
char *test_destroy()
{
mu_assert(queue != NULL, "Failed to make queue #2");
Queue_destroy(queue);
return NULL;
}
char *test_send_recv()
{
int i = 0;
for(i = 0; i < NUM_TESTS; i++) {
Queue_send(queue, tests[i]);
mu_assert(Queue_peek(queue) == tests[0], "Wrong next value.");
}
mu_assert(Queue_count(queue) == NUM_TESTS, "Wrong count on send.");
QUEUE_FOREACH(queue, cur) {
debug("VAL: %s", (char *)cur->value);
}
for(i = 0; i < NUM_TESTS; i++) {
char *val = Queue_recv(queue);
mu_assert(val == tests[i], "Wrong value on recv.");
}
mu_assert(Queue_count(queue) == 0, "Wrong count after recv.");
return NULL;
}
char *all_tests() {
mu_suite_start();
mu_run_test(test_create);
mu_run_test(test_send_recv);
mu_run_test(test_destroy);
return NULL;
}
RUN_TESTS(all_tests);
你應該在不修改測試檔案的條件下,使單元測試能夠執行,並且它應該能夠通過valgrind
而沒有任何記憶體錯誤。下面是當我直接執行stack_tests
時它的樣子:
$ ./tests/stack_tests
DEBUG tests/stack_tests.c:60: ----- RUNNING: ./tests/stack_tests
----
RUNNING: ./tests/stack_tests
DEBUG tests/stack_tests.c:53:
----- test_create
DEBUG tests/stack_tests.c:54:
----- test_push_pop
DEBUG tests/stack_tests.c:37: VAL: test3 data
DEBUG tests/stack_tests.c:37: VAL: test2 data
DEBUG tests/stack_tests.c:37: VAL: test1 data
DEBUG tests/stack_tests.c:55:
----- test_destroy
ALL TESTS PASSED
Tests run: 3
$
queue_test
的輸出基本一樣,所以我在這裡就不展示了。
如何改進
你可以做到的唯一真正的改進,就是把所用的List
換成DArray
。Queue
資料結構難以用DArray
實現,因為它要同時處理兩端的節點。
完全在標頭檔案中來實現它們的缺點,是你並不能夠輕易地對它做效能調優。你需要使用這種技巧,建立一種以特定的方式使用List
的“協議”。做效能調優時,如果你優化了List
,這兩種資料結構都會有所改進。
附加題
-
使用
DArray
代替List
實現Stack
,並保持單元測試不變。這意味著你需要建立你自己的STACK_FOREACH
。