码迷,mamicode.com
首页 > 其他好文 > 详细

数据结构之单链表实现

时间:2015-04-21 20:33:26      阅读:130      评论:0      收藏:0      [点我收藏+]

标签:

这学期开了《数据结构》的课,毫无意外,教材是严蔚敏版的。伪代码看起来还是比较好懂的,但是真自己实现就问题就多了。而且写习惯了Python的代码,再回过来写C代码,突然发现,C代码挺难调试的。写得不好一堆Bug。
打算是在这个学期之内,把所有的数据结构和经典算法,都自己用C语言实现一下。先上代码:

链表的操作并不复杂,但是确实理解和操作链式存储结构的基础。其操作包括,创建一个新的链表并初始化,在链表中进行插入,查找,删除。插入数据的过程中需要用malloc进行动态内存分配,而删除过程中需要用free进行内存释放。这是特别得注意的两个操作,不然很容易造成内存泄露。除了动态内存分配与释放外,基本就是指针的指向操作,这是一个很容易让人犯糊涂的操作。画画简单的示意图是很有帮助的。

以下,链表的头文件,包括节点结构体,函数声明。
<!--more-->
<pre>
/* list.h */
#ifndef _LIST_H
#define _LIST_H

#define OK 1

struct Node;
typedef struct Node *PtrToNode;
typedef PtrToNode List;
typedef PtrToNode Position;
typedef int ElementType;
typedef int Status; /* 返回状态 */

List CreateList(); /* 创建并初始化链表的方法 */
Position Find(ElementType X, List L); /* 查找方法,根据元素查找,返回元素的位置 */
Position FindPrevious(ElementType X, List L); /* 查找方法,根据元素查找,返回元素的上一个节点位置 */
Status Delete(ElementType X, List L); /* 删除方法,根据元素删除节点 */
Status Insert(ElementType X, Position P); /* 插入方法,插入到指定位置之前 */

/* 节点信息结构体,包含数据域与指针域 */
struct Node
{
ElementType Element;
Position next;
};

#endif
/* end list.h */
</pre>

以下是各种方法的实现:
<pre>
/* list.c */
#include &lt;stdlib.h&gt;
#include "list.h"
#include "error.h"

List
CreateList()
{
List L;
L = (List)malloc(sizeof(struct Node));
L->next = NULL;

return L;
}

/* Return the Position of X if found, or return NULL */
Position
Find(ElementType X, List L)
{
Position P;

P = L->next;
while (P != NULL && P->Element != X) {
P = P->next;
}

if (P == NULL)
return NULL;
else
return P;
}

/* Return the Position of X if found, or return NULL */
Position
FindProvious(ElementType X, List L)
{
Position P;

P = L;
while (P->next != NULL && P->next->Element != X) {
P = P->next;
}

if (P->next == NULL)
return NULL;
else
return P;
}

Status
Delete(ElementType X, List L)
{
Position P;
Position tmp;

P = FindProvious(X, L);
if (P == NULL || P->next == NULL)
Error("Not found!");
else {
tmp = P->next;
P->next = P->next->next;
free(tmp);
return OK;
}
}

/* Insert after P */
Status
Insert(ElementType X, Position P)
{
Position Q;

Q = (List)malloc(sizeof(struct Node));
if (Q == NULL)
FatalError("Out of space!");
else {
Q->Element = X;
Q->next = P->next;
P->next = Q;

return OK;
}
}
/* end list.c */
</pre>

考虑到要实现链表,栈,树,图等多种数据结构,为了提高代码重用,因而将错误处理独立出来,分为error.c和error.h文件,包含错误处理的头文件并在编译时,链接其实现文件就可进行错误处理函数的调用。以下,错误处理的头文件和实现:
<pre>
/* error.h */
#ifndef _ERROR_H
#define _ERROR_H

void Error(char *s);
void FatalError(char *s);

#endif
/* end error.h */

/* error.c */
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

void
FatalError(char *s)
{
fprintf(stderr, "Fatal errors: %s\n", s);
exit(-1);
}

void
Error(char *s)
{
fprintf(stderr, "Errors: %s\n", s);
exit(0);
}
/* end error.c */
</pre>

当然,这只是实现了数据结构而已,还要进行测试和调试。实现只是一个开始,花在调试上的时间比花在实现上的时间多多了。也让我意识到,如果实现的代码太差了,调试简直就是煎熬和浪费时间。而且还有可能因为实现代码实在Bug太多,还存在大量的逻辑错误而不得不重新实现的。
以下测试代码:
<pre>
/* test_list.c */
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include "list.h"
#define NODE_NUM 5 /* The num of the list. */

int
main()
{
int i; /* For loop */
int tmp;
List L;
Position P;
void Print(Position P);

L = CreateList();

while (1) {
printf("Please keyin a num to select operation:\n");
printf(" 1.Insert\n 2.Find\n 3.Delete\n 4.Print\n 5.Quit\n");
scanf("%d", &tmp);
if (!(tmp >= 1 && tmp <= 5))
continue;
switch(tmp) {

case 1:
printf("Please input %d num to the list:\n", NODE_NUM);
for (i = 0; i < NODE_NUM; ++i) {
scanf("%d", &tmp);
Insert(tmp, L);
}
Print(L->next);
break;

case 2: {
printf("Please input the value that you want to find: ");
scanf("%d", &tmp);
if (P = Find(tmp, L))
printf("Found!\nIt‘s %d.\n", P->Element);
else
printf("Don‘t find!\n");
} break;

case 3: {
printf("Please input the value that you want to delete: ");
scanf("%d", &tmp);
if (Delete(tmp, L))
printf("Successfully delete!\n");
} break;

case 4: Print(L->next); break;

case 5:
exit(0);
}
}

return 0;
}

void Print(Position P)
{
while (P != NULL) {
printf("%d\t", P->Element);
P = P->next;
}
printf("\n");
}
/* end test_list.c */
</pre>

在ubuntu 14.04,用gcc测试并运行的过程和结果如下。

在终端输入命令: gcc list.c error.c test_list.c -o test_list
将链表实现文件,错误处理文件,测试用的主文件,多文件一起编译链接,并使用-o参数将编译链接后的可执行文件重命名文test_list。
在终端输入命令: ./test_list 运行程序,得到如下结果:<a href="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172152屏幕截图.png"><img src="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172152屏幕截图.png" alt="2015-04-21 17:21:52屏幕截图" width="706" height="134" class="aligncenter size-full wp-image-197" /></a>

输入 1,进行插入操作,插入完成后,并且打印输入的序列:
<a href="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172303屏幕截图.png"><img src="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172303屏幕截图.png" alt="2015-04-21 17:23:03屏幕截图" width="716" height="190" class="aligncenter size-full wp-image-196" /></a>

输入2,进行查找操作,如果找到的返回查找成功信息,如果未找到则返回"Not found!":
<a href="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172331屏幕截图.png"><img src="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172331屏幕截图.png" alt="2015-04-21 17:23:31屏幕截图" width="714" height="200" class="aligncenter size-full wp-image-195" /></a>

输入3,进行删除操作,先进行查找,如果存在则删除,如果不存在,返回未找到的提示信息:
成功删除:
<a href="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172411屏幕截图.png"><img src="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172411屏幕截图.png" alt="2015-04-21 17:24:11屏幕截图" width="721" height="278" class="aligncenter size-full wp-image-193" /></a>

未找到:
<a href="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172451屏幕截图.png"><img src="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172451屏幕截图.png" alt="2015-04-21 17:24:51屏幕截图" width="717" height="351" class="aligncenter size-full wp-image-191" /> </a>

输入4,打印链表中的元素:
<a href="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172352屏幕截图.png"><img src="http://www.qyspaces.com/wp-content/uploads/2015/04/2015-04-21-172352屏幕截图.png" alt="2015-04-21 17:23:52屏幕截图" width="713" height="426" class="aligncenter size-full wp-image-194" /></a>

由于主循环是一个while(1)的无限循环,所以每次进行完一个操作后,都会显示进行选择操作。最后,输入5则退出程序。
程序存在一个致命的Bug,那就是,在选择操作时,如果不按照指示的操作只输入数字,而输入字符的话,会导致程序崩溃。因为我在程序中的switch分支语句时,分支使用的是int型数据进行比较判断。我也尝试过用字符,即输入到一个字符中,然后将输入的那些数字当做字符来进行比较。这样,无论输入什么,只要进行适当错误处理,都不会出现程序崩溃的情况。但是呢,由于输入缓冲的原因,会导致上一次输入完成后的回车,被冲洗进入下一次主菜单的选择中,而导致出现两个主菜单的情况。如下:
<a href="http://www.qyspaces.com/wp-content/uploads/2015/04/ceshi.png"><img src="http://www.qyspaces.com/wp-content/uploads/2015/04/ceshi.png" alt="ceshi" width="714" height="425" class="aligncenter size-full wp-image-206" /></a>

这种情况要怎么解决还是一个问题,应该可以考虑提前冲洗缓冲区。

数据结构之单链表实现

标签:

原文地址:http://www.cnblogs.com/qiuyi116/p/4445146.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!