模拟手机通讯录——双向循环链表的实现与应用

模拟手机通讯录——双向循环链表的实现与应用

前言

第一次数据结构实验,要求不高,纯粹是复习之前的编程知识与技巧。这是我第一次编写一个玩具项目,也是第一次尝试多文件编程。我写下这篇博文,留作纪念。

数据结构——双向循环链表

程序设计中最重要的不是算法的设计,而是数据结构的选择。选择合适的数据结构可以使编程更加轻松,流畅。手机通讯录的一条一条信息,本质上是一个线性结构。这里我选择双向循环链表,虽然无法实现二分查找,导致查找的时间复杂度为O(n),但是链表的插入和删除非常方便,时间复杂度为O(1),选择双向链表,是因为双向链表在结点的前面或者后面插入或是删除新结点都很方便,时间均为O(1)。如果是单向链表,向后插入和删除结点固然方便,时间为O(1)但是结点的前面插入删除还要查找前驱结点,时间就变成了O(n)。

接口定义

声明 描述
DList* init(); 初始化双向循环链表,返回一个指向指针,该指针指向双向循环链表类型DList
int dlist_ins_next(DList *dlist, Node *element, DataType data); 在链表dlist的结点element后面插入一个新元素,新元素的数据为data。插入成功,返回1。否则返回0。
int dlist_ins_prev(DList *dlist, Node *element, DataType data); 在链表dlist的结点element前面插入一个新元素,新元素的数据为data。插入成功,返回1;否则返回0。
int dlist_remove(DList *dlist, Node *element); 在链表dlist中删除结点element。删除成功,返回1;否则返回0。
Node* dlist_find(DList *dlist, DataType data, int (*cmp)(DataType, DataType)); 在链表dlist中查找一个元素,并返回查找到的结点的指针。如果未找到,返回NULL。查找的标准由用户自定义的比较函数cmp提供,比较的对象是data。假如类型DataType可以比较大小,如果要找一个比data大的结点,cmp函数可以写成:如果函数cmp参数表中左边的参数比右边的参数大,返回1;否则返回0。
void dlist_destroy(DList *dlist); 销毁链表dlist
int dlist_size(DList *dlist) 这是一个宏,给出链表dlist中元素的个数
Node* dlist_head(DList *dlist) 这是一个宏,返回链表dlist的头结点
Node* dlist_tail(DList *dlist) 这是一个宏,返回链表dlist的尾结点

双向循环链表的实现

为了实现代码的复用,同时也为了让我的组员能够用轻松的编写模拟短信、备忘录的小程序,我把双向循环链表的声明和实现分离开来,提供头文件和实现文件。这样我的组员就可以直接调用函数完成双向链表的操作,无需关注实现的细节。

双向循环链表的头文件

/* dlist.h */
#ifndef DLIST_H
#define DLIST_H

#define NAME_LEN 10
#define TEL_LEN  20
#define TEXT_LEN 100

/*手机通讯录的结构*/
typedef struct {
    char name[NAME_LEN];
    char tel[TEL_LEN];
    /*下面两个数据是我的组员要用到的*/
    char text[TEXT_LEN];  /*文本内容*/
    int year, month, day; /*年月日*/
}DataType;

/*双向循环链表的结点*/
typedef struct node {
    DataType    data;
    struct node *pre;
    struct node *next;
}Node;

/*双向循环链表*/
typedef struct dlist {
    int size;
    Node *head;
    Node *tail;
}DList;

DList* init();
int dlist_ins_next(DList *dlist, Node *element, DataType data);
int dlist_ins_prev(DList *dlist, Node *element, DataType data);
int dlist_remove(DList *dlist, Node *element);
Node* dlist_find(DList *dlist, DataType data, int (*cmp)(DataType, DataType));
void dlist_destroy(DList *dlist);

#define dlist_size(dlist) ((dlist)->size)
#define dlist_head(dlist) ((dlist)->head)
#define dlist_tail(dlist) ((dlist)->tail) 

#endif /* DLIST_H */

双向循环链表的实现文件

#include 
#include 
#include 
#include "dlist.h"

DList* init()
{
    DList *dlist;
    if ((dlist = (DList*)malloc(sizeof(DList))) != NULL) {
        dlist->size = 0;
        dlist->head = NULL;
        dlist->tail = NULL;
    }
    return dlist;
}

int dlist_ins_next(DList *dlist, Node *element, DataType data)
{
    Node *new_element = (Node*)malloc(sizeof(Node));
    if (new_element == NULL)
        return 0;
    new_element->data = data;

    if (dlist_size(dlist) == 0) {
        dlist->head = dlist->tail = new_element;
        dlist->head->pre = dlist->tail;
        dlist->tail->next = dlist->head;
    } else {
        new_element->pre = element;
        new_element->next = element->next;
        element->next = new_element;
        new_element->next->pre = new_element;
    }

    if (element == dlist->tail)
        dlist->tail = new_element;

    dlist->size++;
    return 1;
}

int dlist_ins_prev(DList *dlist, Node *element, DataType data)
{
    Node *new_element = (Node *)malloc(sizeof(Node));
    if (new_element == NULL) 
        return 0;
    new_element->data = data;

    if (dlist_size(dlist) == 0) {
        dlist->head = dlist->tail = new_element;
        dlist->head->pre = dlist->tail;
        dlist->tail->next = dlist->head;
    } else {
        new_element->next = element;
        new_element->pre = element->pre;
        element->pre = new_element;
        new_element->pre->next = new_element;
    }

    if (element == dlist->head)
        dlist->head = new_element;

    dlist->size++;
    return 1;
}

int dlist_remove(DList *dlist, Node *element)
{
    if (element == NULL || dlist_size(dlist) == 0)
        return 0;

    if (element == dlist->head)
        dlist->head = element->next;
    else if (element == dlist->tail)
        dlist->tail = element->pre;

    element->pre->next = element->next;
    element->next->pre = element->pre;
    free(element);

    dlist->size--;
    return 1;
}
Node* dlist_find(DList *dlist, DataType data, int (*cmp)(DataType, DataType))
{
    Node *t = dlist->head;
    int num = 0;
    while (num < dlist_size(dlist) && !cmp(t->data, data)) {
        t = t->next;
        num++;
    }
    if (num == dlist_size(dlist)) 
        return NULL;
    else
        return t;
}

void dlist_destroy(DList *dlist)
{
    Node *p = dlist->head;
    while (dlist_size(dlist)) {
        Node *q = p->next;
        free(p);
        p = q;
        dlist->size--;
    }
    dlist->head = NULL;
    dlist->tail = NULL;
    return;
}

模拟手机通讯录

模拟手机通讯录,说是模拟,因为数据都在内存中,程序结束,数据也就“没有”了,没有什么实用价值。如果把数据保存在文件中,勉强可以用一用。不过这次实验的目的不在完成项目,而是数据结构的实现,所以无所谓了。

我的手机通讯录有以下功能

  • 浏览联系人
  • 查找联系人
  • 增加联系人
  • 修改联系人资料
  • 删除联系人
  • 删除所有联系人

代码实现

/* dlist.c */
#include 
#include 
#include 
#include 
#include "dlist.h"

void view(DList *dlist);
void add(DList *dlist);
void search(DList *dlist);
void del(DList *dlist);
int change(DList *dlist);
int cmp_add(DataType a, DataType b);
int cmp_find(DataType a, DataType b);

int main()
{
    DList *dlist = init();
    if (dlist == NULL) {
        printf("创建失败!\n");
        exit(0);
    }
    while (1) {
        char ch;
        printf("***************************\n");
        printf("      模拟手机通讯录\n");
        printf("***************************\n");
        printf("欢迎!\n");
        printf("菜单:\n");
        printf("(1)-----浏览通讯录\n");
        printf("(2)-----查找联系人\n");
        printf("(3)-----增加联系人\n");
        printf("(4)-----修改联系人资料\n");
        printf("(5)-----删除联系人\n");
        printf("(6)-----删除所有联系人\n");
        printf("(7)-----退出\n");
        printf("> ");
        scanf(" %c", &ch);
        getchar();
        switch(ch) {
            case '1': view(dlist);   break;
            case '2': search(dlist); break;
            case '3': add(dlist);    break;
            case '4': change(dlist); break;
            case '5': del(dlist);    break;
            case '6':
                dlist_destroy(dlist);
                printf("全部删除!\n");
                break;
            case '7':
                dlist_destroy(dlist);
                exit(0);
                break;
            default:
                printf("请重新输入!\n");
                break;
        }
        printf("\n继续?(y/n)\n> ");

        scanf(" %c", &ch);
        if (ch == 'y' || ch == 'Y')
            system("cls");
        else {
            printf("结束\n");
            break;
        }
    }

    return 0;
}

int cmp_add(DataType a, DataType b)
{
    if (strcmp(a.name, b.name) > 0)
        return 1;
    else
        return 0;
}

int cmp_find(DataType a, DataType b)
{
    if (strcmp(a.name, b.name) == 0)
        return 1;
    else
        return 0;
}

int change(DList *dlist)
{
    DataType data;
    Node *tem;
    char ch;
    printf("输入要修改的联系人姓名:\n");
    gets(data.name);
    tem = dlist_find(dlist, data, cmp_find);
    if (tem == NULL) {
        printf("未找到联系人!\n");
        return 0;
    }
    else {
        data = tem->data;
        dlist_remove(dlist, tem);
    }
    printf("修改姓名输入n,修改号码输入t:\n");
    scanf(" %c", &ch);
    getchar();
    if (ch == 'n') {
        printf("输入姓名:\n");
        gets(data.name);
    } else {
        printf("输入手机号:\n");
        scanf("%s", data.tel);
    }

    if (!dlist_size(dlist)) {
        if (dlist_ins_next(dlist, dlist_head(dlist), data))
            printf("修改成功!\n");
        else
            printf("修改失败!\n");
    } else {
        Node *p = dlist_find(dlist, data, cmp_add);
        //结点在尾部
        if (p == NULL
        && dlist_ins_next(dlist, dlist_tail(dlist), data))
        {
            printf("修改成功!\n");
        }
        else if (dlist_ins_prev(dlist, p, data)) {
            printf("修改成功!\n");
        } else {
            printf("修改失败\n");
        }
    }
    return 1;
}

void search(DList *dlist)
{
    DataType data;
    Node *tem;
    printf("输入要查找的联系人姓名:\n");
    gets(data.name);
    tem = dlist_find(dlist, data, cmp_find);
    if (tem == NULL)
        printf("未找到联系人!\n");
    else {
        printf("前一联系人:\n"
               "姓名:      %s\n"
               "手机号: %s\n\n", tem->pre->data.name, tem->pre->data.tel);
        printf("当前联系人:\n"
               "姓名:   %s\n"
               "手机号: %s\n\n", tem->data.name, tem->data.tel);
        printf("后一联系人:\n"
               "姓名:   %s\n"
               "手机号: %s\n", tem->next->data.name, tem->next->data.tel);
    }
}

void view(DList *dlist)
{
    if (dlist_size(dlist)) {
        Node *p = dlist_head(dlist);
        int num = 1;
        while (num <= dlist_size(dlist)) {
            printf("%d\n", num);
            printf("姓名:   %s\n"
                   "手机号: %s\n", p->data.name, p->data.tel);
            p = p->next;
            num++;
        }
    } else {
        printf("None!\n");
    }
}

void add(DList *dlist)
{
    DataType data;
    printf("输入姓名:\n");
    gets(data.name);
    printf("输入手机号:\n");
    scanf("%s", data.tel);
    if (!dlist_size(dlist)) {
        if (dlist_ins_next(dlist, dlist_head(dlist), data))
            printf("增加成功!\n");
        else
            printf("增加失败!\n");
    } else {
        Node *p = dlist_find(dlist, data, cmp_add);
        //结点在尾部
        if (p == NULL
        && dlist_ins_next(dlist, dlist_tail(dlist), data))
        {
            printf("增加成功!\n");
        }
        else if (dlist_ins_prev(dlist, p, data)) {
            printf("增加成功!\n");
        } else {
            printf("增加失败\n");
        }
    }
}

void del(DList *dlist)
{
    DataType data;
    Node *tem;
    printf("输入要删除的联系人姓名:\n");
    gets(data.name);
    tem = dlist_find(dlist, data, cmp_find);
    if (tem == NULL) {
        printf("未找到联系人,删除失败!\n");
    } else if (dlist_remove(dlist, tem)) {
        printf("删除成功!\n");
    } else {
        printf("删除失败!\n");
    }
}

复用,复用——浅谈泛型和C++中的模版template

声明

本人对C++完全不懂,但是为了实现泛型,不得以使用C++中的一些特性(虽然纯粹的C语言可以用void*指针来实现泛型,不过由于老师要求要用C++中的template,所以我没有这么做)。读者可以把下面的代码看作是一个扩展的C,而不是C++。请不要吐槽我对数据结构的封装做的不彻底,因为我真的不懂C++,依葫芦画瓢使用template已经是我的极限了。这里我只用struct,没有用class(我感觉两者区别很小)。

头文件(勉强的C++风格)

//dlist.h
#ifndef DLIST_H
#define DLIST_H

template <typename T>
struct Node {
    T data;
    Node *pre;
    Node *next;
};

template <typename T>
struct DList {
private:
    int num;
    Node *head;
    Node *tail;
public:
    DList()
    {
        head = tail = NULL;
        num = 0;
    }
    int size();
    Node* begin();
    Node* end();
    bool ins_next(Node *element, T data);
    bool ins_prev(Node *element, T data);
    bool remove(Node *element);
    Node* find(T data, bool (*cmp)(T, T));
    void clear();
};


#include "dlist.cpp"
#endif /* DLIST_H */

实现文件

//dlist.cpp
#include 
#include 
#include 
#include "dlist.h"

template <typename T>
Node* DList::begin()
{
    return head;
}

template <typename T>
Node* DList::end()
{
    return tail;
}

template <typename T>
int DList::size()
{
    return num;
}

template <typename T>
bool DList::ins_next(Node *element, T data)
{
    Node *new_element = new Node;
    if (new_element == NULL)
        return false;
    new_element->data = data;

    if (num == 0) {
        head = tail = new_element;
        head->pre = tail;
        tail->next = head;
    } else {
        new_element->pre = element;
        new_element->next = element->next;
        element->next = new_element;
        new_element->next->pre = new_element;
    }

    if (element == tail)
        tail = new_element;

    num++;
    return true;
}

template <typename T>
bool DList::ins_prev(Node *element, T data)
{
    Node *new_element = new Node;
    if (new_element == NULL)
        return false;
    new_element->data = data;

    if (num == 0) {
        head = tail = new_element;
        head->pre = tail;
        tail->next = head;
    } else {
        new_element->next = element;
        new_element->pre = element->pre;
        element->pre = new_element;
        new_element->pre->next = new_element;
    }

    if (element == head)
        head = new_element;

    num++;
    return true;
}

template <typename T>
bool DList::remove(Node *element)
{
    if (element == NULL || num == 0)
        return false;

    if (element == head)
        head = element->next;
    else if (element == tail)
        tail = element->pre;

    element->pre->next = element->next;
    element->next->pre = element->pre;
    delete element;

    num--;
    return true;
}

template <typename T>
Node* DList::find(T data, bool (*cmp)(T, T))
{
    Node *t = head;
    int cnt = 0;
    while (cnt < num && !cmp(t->data, data)) {
        t = t->next;
        cnt++;
    }
    if (num == cnt)
        return NULL;
    else
        return t;
}

template <typename T>
void DList::clear()
{
    Node *p = head;
    while (num) {
        Node *q = p->next;
        delete p;
        p = q;
        num--;
    }
    head = NULL;
    tail = NULL;
}

通讯录的重新实现

#include 
#include 
#include 
#include 
#include 
#include "dlist.h"
using namespace std;

const int NAME_LEN = 10;
const int TEL_LEN  = 20;

struct DataType {
    char name[NAME_LEN];
    char tel[TEL_LEN];
};

void view(DList &dlist);
void add(DList &dlist);
void search(DList &dlist);
bool change(DList &dlist);
void del(DList &dlist);
bool cmp_add(DataType a, DataType b);
bool cmp_find(DataType a, DataType b);

int main()
{
    DList dlist;
    while (1) {
        char ch;
        printf("***************************\n");
        printf("      模拟手机通讯录\n");
        printf("***************************\n");
        printf("欢迎!\n");
        printf("菜单:\n");
        printf("(1)-----浏览通讯录\n");
        printf("(2)-----查找联系人\n");
        printf("(3)-----增加联系人\n");
        printf("(4)-----修改联系人信息\n");
        printf("(5)-----删除联系人\n");
        printf("(6)-----删除所有联系人\n");
        printf("(7)-----退出\n");
        printf("> ");
        scanf(" %c", &ch);
        getchar();
        switch(ch) {
            case '1': view(dlist);   break;
            case '2': search(dlist); break;
            case '3': add(dlist);    break;
            case '4': change(dlist); break;
            case '5': del(dlist);    break;
            case '6':
                dlist.clear();
                printf("全部删除!\n");
                break;
            case '7':
                dlist.clear();
                exit(0);
                break;
            default:
                printf("请重新输入!\n");
                break;
        }
        printf("\n继续?(y/n)\n> ");

        scanf(" %c", &ch);
        if (ch == 'y' || ch == 'Y')
            system("cls");
        else
            break;
    }

    return 0;
}

bool cmp_add(DataType a, DataType b)
{
    if (strcmp(a.name, b.name) > 0)
        return true;
    else
        return false;
}

bool cmp_find(DataType a, DataType b)
{
    if (strcmp(a.name, b.name) == 0)
        return true;
    else
        return false;
}

bool change(DList &dlist)
{
    DataType data;
    Node *tem;
    char ch;
    printf("输入要修改的联系人姓名:\n");
    gets(data.name);
    tem = dlist.find(data, cmp_find);
    if (tem == NULL) {
        printf("未找到联系人!\n");
        return false;
    } else {
        data = tem->data;
    }

    printf("修改姓名输入n,修改号码输入t\n");
    scanf(" %c", &ch);
    getchar();
    if (ch == 'n') {
        printf("输入姓名:\n");
        gets(tem->data.name);
    } else {
        printf("输入手机号:\n");
        scanf("%s", tem->data.tel);
    }
    while (tem != dlist.end()
    && strcmp(tem->data.name, tem->next->data.name) > 0)
    {
        swap(tem->data, tem->next->data);
        tem = tem->next;
    }
    while (tem != dlist.begin()
    && strcmp(tem->data.name, tem->pre->data.name) < 0)
    {
        swap(tem->data, tem->pre->data);
        tem = tem->pre;
    }

    return true;
}

void search(DList &dlist)
{
    DataType data;
    Node *tem;
    printf("输入要查找的联系人姓名:\n");
    gets(data.name);
    tem = dlist.find(data, cmp_find);
    if (tem == NULL)
        printf("未找到联系人!\n");
    else
        printf("姓名:      %s\n"
               "手机号: %s\n", tem->data.name, tem->data.tel);
}

void view(DList &dlist)
{
    if (dlist.size()) {
        Node *p = dlist.begin();
        int num = 1;
        while (num <= dlist.size()) {
            printf("%d\n", num);
            printf("姓名:   %s\n"
                   "手机号: %s\n", p->data.name, p->data.tel);
            p = p->next;
            num++;
        }
    } else {
        printf("None!\n");
    }
}

void add(DList &dlist)
{
    DataType data;
    printf("输入姓名:\n");
    gets(data.name);
    printf("输入手机号:\n");
    scanf("%s", data.tel);
    if (!dlist.size()) {
        if (dlist.ins_next(dlist.begin(), data))
            printf("增加成功!\n");
        else
            printf("增加失败!\n");
    } else {
        Node *p = dlist.find(data, cmp_add);
        //结点在尾部
        if (p == NULL
        && dlist.ins_next(dlist.end(), data))
        {
            printf("增加成功!\n");
        }
        else if (dlist.ins_prev(p, data)) {
            printf("增加成功!\n");
        } else {
            printf("增加失败\n");
        }
    }
}

void del(DList &dlist)
{
    DataType data;
    Node *tem;
    printf("输入要删除的联系人姓名:\n");
    gets(data.name);
    tem = dlist.find(data, cmp_find);
    if (tem == NULL) {
        printf("未找到联系人,删除失败!\n");
    } else if (dlist.remove(tem)) {
        printf("删除成功!\n");
    } else {
        printf("删除失败!\n");
    }
}

总结和反思

实话实说,我的编程技术从开学到现在没有丝毫长进。前不久刚考完计算机二级,个人感觉不错。计算机二级的C语言实在是太基础了,和真正的编程还有很大的区别。不过我并不想成为一个真正的程序员,所以就算我不会使用IDE,不会使用linux,不会用git进行版本管理,不会用自动化测试工具测试我的代码,我也没什么羞愧的。我目前只会使用C语言,而且用到还不好。我已经下定决心,专门练习算法竞赛的相关知识和编程技巧。也许我这辈子都写不出一个有着漂亮界面,而且非常实用的“真正”的程序,不过我不在乎。我想成为一个“聪明人”,一个可以用计算机解决复杂问题题的“竞技编程达人”,就是我不是一个“万金油”。

你可能感兴趣的:(杂谈,数据结构笔记,链表,数据结构,编程)