作为一名长期奋战在系统开发一线的程序员,我深知数据结构是编程能力的根基。本周我系统梳理了链表、栈和二叉树这三种基础数据结构,在实现过程中积累了不少实战经验,尤其对边界条件的处理有了更深刻的认识。
在带头节点的单链表实现中,我特别注重接口的统一性和安全性。以下是核心实现要点:
c复制typedef struct Node {
int data;
struct Node *next;
} ListNode;
typedef struct {
ListNode *head;
size_t size;
} LinkedList;
// 初始化链表
void initList(LinkedList *list) {
list->head = (ListNode *)malloc(sizeof(ListNode));
list->head->next = NULL;
list->size = 0;
}
关键技巧:使用哨兵节点(head)可以统一插入/删除操作的处理逻辑,避免对第一个节点的特殊处理。
边界检查的典型场景包括:
内存管理要点:
二叉树的递归遍历看似简单,但隐藏着不少学问。以前序遍历为例:
c复制void preOrder(TreeNode *root) {
if (root == NULL) return;
printf("%d ", root->data); // 先访问根节点
preOrder(root->left); // 再遍历左子树
preOrder(root->right); // 最后遍历右子树
}
递归的天然优势在于:
但需要注意:
链式栈相比数组实现的栈更灵活,但要注意指针操作:
c复制typedef struct StackNode {
int data;
struct StackNode *next;
} StackNode;
void push(StackNode **top, int data) {
StackNode *newNode = (StackNode *)malloc(sizeof(StackNode));
newNode->data = data;
newNode->next = *top;
*top = newNode;
}
常见陷阱:
通过打印日志观察对象生命周期:
cpp复制class Test {
public:
Test() { cout << "Constructor" << endl; }
~Test() { cout << "Destructor" << endl; }
};
void func(Test t) { // 值传递
Test local; // 局部对象
} // local析构,t的拷贝析构
int main() {
Test t1; // 构造t1
func(t1); // 拷贝构造参数
return 0; // t1析构
}
输出顺序:
关键区别:
| 特性 | 栈对象 | 堆对象 |
|---|---|---|
| 生命周期 | 作用域结束自动释放 | 需要手动delete |
| 分配速度 | 快 | 慢 |
| 大小限制 | 受栈大小限制 | 受内存大小限制 |
| 多线程安全性 | 线程私有 | 需要同步机制 |
通过实验验证两者对符号链接的处理差异:
c复制struct stat sb;
char *path = "symlink";
// 跟随符号链接
if (stat(path, &sb) == -1) {
perror("stat");
}
// 不跟随符号链接
if (lstat(path, &sb) == -1) {
perror("lstat");
}
关键发现:
实现时需要注意:
核心代码结构:
c复制void traverse(const char *path) {
DIR *dir = opendir(path);
if (!dir) return;
struct dirent *entry;
while ((entry = readdir(dir))) {
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0)
continue;
char fullpath[PATH_MAX];
snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);
struct stat sb;
if (lstat(fullpath, &sb) == -1) {
perror("lstat");
continue;
}
if (S_ISDIR(sb.st_mode)) {
traverse(fullpath); // 递归子目录
} else {
process_file(fullpath);
}
}
closedir(dir);
}
推荐工具:
典型错误案例:
c复制char *str = malloc(100);
if (error) {
return; // 直接返回导致泄漏
}
free(str);
对于深度不确定的递归:
二叉树遍历的迭代实现示例:
c复制void inorderTraversal(TreeNode *root) {
Stack *s = createStack();
TreeNode *curr = root;
while (curr || !isEmpty(s)) {
while (curr) {
push(s, curr);
curr = curr->left;
}
curr = pop(s);
printf("%d ", curr->val);
curr = curr->right;
}
}
基础模板:
makefile复制CC = gcc
CFLAGS = -Wall -g
TARGET = program
SRCS = main.c list.c tree.c
OBJS = $(SRCS:.c=.o)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f $(OBJS) $(TARGET)
高级技巧:
经过这周的密集编码训练,我最大的体会是:理论理解必须通过实践来巩固。比如递归的栈消耗问题,只有在实际调试中遇到栈溢出错误,才能真正理解其严重性。建议每个学习数据结构的同学都亲自动手实现一遍基础结构,这比单纯看书要有效得多。