数据结构之单链表实现

这学期开了《数据结构》的课,毫无意外,教材是严蔚敏版的。伪代码看起来还是比较好懂的,但是真自己实现就问题就多了。而且写习惯了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>

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

时间: 2024-08-02 00:14:27

数据结构之单链表实现的相关文章

C#数据结构-单链表

理论基础: 链表是用一组任意的存储单元来存储线性表中的数据元素. 如果结点的引用域只存储该结点直接后继结点的存储地址,则该链表叫单链表(Singly Linked List). 单链表由头引用H唯一确定.头引用指向单链表的第一个结点,也就是把单链表第一个结点的地址放在H中. C#实现: 1接口 引用线性表的接口IListDS<T> 2实现 首先,必须定义一个单链表的节点类.  1 public class Node<T> 2    { 3        private T data

数据结构之单链表(C++实现)

有一段时间没有写一些关于数据结构的程序了,正好算法导论这门课学到红黑树,感觉数据结构都忘得差不多了,而且考研还要考,故再拿来复习一下. 一.C++实现单链表 #include<iostream> using namespace std; typedef struct LNode { int data; struct LNode *next; }LNode,*LinkList; void CreateList_L(LinkList &L, int n) { L = new LNode()

数据结构之——单链表

今天闲来无事,就打算把大一的时候写过的数据结构重温一遍,基本上我在大一之后只在单片机上用过几次顺序表和循环队列之外再就很少使用过数据结构了. 并且乘着写一下数据结构也可以熟悉熟悉vim. 首先定义单链表节点: 1 #define DataType int 2 3 struct node{ 4 DataType data; 5 struct node *next; 6 }; 7 struct node list,*p_list; 单链表这个数据结构需要一些函数来对她操作,一般需要有这些: 1.初始

数据结构(一) 单链表的实现-JAVA

数据结构还是很重要的,就算不是那种很牛逼的,但起码得知道基础的东西,这一系列就算是复习一下以前学过的数据结构和填补自己在这一块的知识的空缺.加油.珍惜校园中自由学习的时光.按照链表.栈.队列.排序.数组.树这种顺序来学习数据结构这门课程把. -WH 一.单链表的概念 链表是最基本的数据结构,其存储的你原理图如下图所示 上面展示的是一个单链表的存储原理图,简单易懂,head为头节点,他不存放任何的数据,只是充当一个指向链表中真正存放数据的第一个节点的作用,而每个节点中都有一个next引用,指向下一

Java数据结构之单链表

链表的组成:链表头+结点 ? ?链表头一般只存储下一个节点的引用 ? ?节点:存数据+下一个节点的引用 链表头代码: package?com.xingej.algorithm.datastructure.linkedList.singleLinkedList; /** ?*?声明一个链表的头部 ?*? ?*?从链表头的使用,联想到hadoop,spark,netty中都有上下文context ?*? ?*[email protected]?erjun?2017年12月8日?上午8:45:08 ?

java数据结构:单链表常见操作代码实现

一.概述: 本文主要总结单链表常见操作的实现,包括链表结点添加.删除:链表正向遍历和反向遍历.链表排序.判断链表是否有环.是否相交.获取某一结点等. 二.概念: 链表: 一种重要的数据结构,HashMap等集合的底层结构都是链表结构.链表以结点作为存储单元,这些存储单元可以是不连续的.每个结点由两部分组成:存储的数值+前序结点和后序结点的指针.即有前序结点的指针又有后序结点的指针的链表称为双向链表,只包含后续指针的链表为单链表,本文总结的均为单链表的操作. 单链表结构: Java中单链表采用No

数据结构之单链表

1. 链表的特点 链表是一种非线性.非顺序的物理结构,是由若干个节点组成. 链表采用的是“见缝插针”的存储方法,不要求内存连续,靠next指针关联起来. 链表的物理存储方式为随机存储,访问方式为顺序访问. 查找节点的时间复杂度为O(n),插入.删除节点的时间复杂度为O(1). 链表适用于写操作多,读操作少的场景. 1 //单向链表节点的数据结构 2 struct SingleListNode 3 { 4 int nData;//当前节点的数据 5 Node* pNext;//指向下一个节点的指针

数据结构关于单链表的一些操作的源代码

单链表的可以有许多问题,这是我特意整理一下的有关他的相关操作,给出代码,有需要的可以自己调试,重要的就是关于环的一些操作: #include <iostream>#include <cstdio>#include <cstdlib>#include <ctime>using namespace std;typedef int Elemtype;typedef struct Node{ Elemtype data; struct Node *next;}Nod

数据结构:单链表结构字符串(python版)

1 #!/urs/bin/env python 2 # -*- coding:utf-8 -*- 3 4 #异常类 5 class stringTypeError(TypeError): 6 pass 7 8 #节点类 9 class Node(object): 10 def __init__(self, elem, next_ = None): 11 self.elem = elem 12 self.next = next_ 13 #单链表类 14 class single_list(obje

数据结构 C++ 单链表 一元多项式的相加

#include <iostream> using namespace std; struct Node { double coe; //系数 int exp; //指数 Node *next; }; void CreatPoly(Node *&head, int n) // 生成带表头结点的单链表,除头结点外另生成n个结点 { head = (Node *)new Node; head->coe = 0; head->exp = 0; head->next = NU