数据结构课程的实践方法指导

  有不少人说数据结构课程抽象,学习起来感到困难。有些同学放弃了,有些同学拿出了高中学习时练熟的功夫,强行理解,死记硬背。结果辛苦不少。效果不佳。反倒得到非常多枯燥的感受。


  其实。数据结构课中有不少理论性分析的内容。但对于本科生学习的内容,以及要达到目标,还是以实践性为主的。在学习的方法上做出改变,这门课程就能够展示出生动的实践性味道来。这就要求在学习过程中,将实践学习有效地开展下去。诸多的困境即能够破解。
  学习数据结构和算法的过程中做出的这些实践。也便成了程序设计能力提高中的重要部分。
  本文就以数据结构课程开篇的“线性表的顺序存储”部分为例,给出两种实践路线的演示样例。

一、在一个程序文件里验证算法

  參考教材中的解说,我们能够将教材中须要掌握的算法合并成一个程序。让程序在计算上跑起来。在输入、调试、測试的过程中。完毕了掌握算法的目的。
  本文演示样例基于李春葆老师的《数据结构教程(第4版)》给出,使用其它教材的做法相似:
  第一步:定义数据的存储结构
  比如。对于线性结构,能够写下:

#define MaxSize 50    //Maxsize将用于后面定义存储空间的大小
typedef int ElemType;  //ElemType在不同场合能够依据问题的须要确定,在此取简单的int
typedef struct
{
    ElemType data[MaxSize];  //利用了前面MaxSize和ElemType的定义
    int length;
} SqList;

  第二步:实现各种基本操作
  比如,对于线性表的顺序存储

//用数组创建线性表
void CreateList(SqList *&L, ElemType a[], int n)
{
    int i;
    L=(SqList *)malloc(sizeof(SqList));
    for (i=0; i<n; i++)
        L->data[i]=a[i];
    L->length=n;
}

//初始化线性表InitList(L)
void InitList(SqList *&L)   //引用型指针
{
    L=(SqList *)malloc(sizeof(SqList));
    //分配存放线性表的空间
    L->length=0;
}

//销毁线性表DestroyList(L)
void DestroyList(SqList *&L)
{
    free(L);
}

//判定是否为空表ListEmpty(L)
bool ListEmpty(SqList *L)
{
    return(L->length==0);
}

//求线性表的长度ListLength(L)
int ListLength(SqList *L)
{
    return(L->length);
}

//输出线性表DispList(L)
void DispList(SqList *L)
{
    int i;
    if (ListEmpty(L)) return;
    for (i=0; i<L->length; i++)
        printf("%d ",L->data[i]);
    printf("
");
}

//求某个数据元素值GetElem(L,i,e)
bool GetElem(SqList *L,int i,ElemType &e)
{
    if (i<1 || i>L->length)  return false;
    e=L->data[i-1];
    return true;
}

//按元素值查找LocateElem(L,e)
int LocateElem(SqList *L, ElemType e)
{
    int i=0;
    while (i<L->length && L->data[i]!=e) i++;
    if (i>=L->length)  return 0;
    else  return i+1;
}

//插入数据元素ListInsert(L,i,e)
bool ListInsert(SqList *&L,int i,ElemType e)
{
    int j;
    if (i<1 || i>L->length+1)
        return false;   //參数错误时返回false
    i--;            //将顺序表逻辑序号转化为物理序号
    for (j=L->length; j>i; j--) //将data[i..n]元素后移一个位置
        L->data[j]=L->data[j-1];
    L->data[i]=e;           //插入元素e
    L->length++;            //顺序表长度增1
    return true;            //成功插入返回true
}

//删除数据元素ListDelete(L,i,e)
bool ListDelete(SqList *&L,int i,ElemType &e)
{
    int j;
    if (i<1 || i>L->length)  //參数错误时返回false
        return false;
    i--;        //将顺序表逻辑序号转化为物理序号
    e=L->data[i];
    for (j=i; j<L->length-1; j++) //将data[i..n-1]元素前移
        L->data[j]=L->data[j+1];
    L->length--;              //顺序表长度减1
    return true;              //成功删除返回true
}

   第三步。编写測试函数,对于每个基本运算的实现进行測试
  首先。应该在程序头部加上必要的头文件(假设不加,出现编译错误倒是也会对付)。

#include <stdio.h>
#include <malloc.h>

  測试函数一般“宜小不宜大”。也就是说,一次尽可能測试尽可能少的基本操作。只是。在必要的时候。也须要调用别的基本操作,用来完毕辅助性的工作。比如。以下的main函数。目标是測试创建顺序表的函数CreateList。创建中须要的数组x能够在程序中直接初始化,而创建的结果,希望通过输出进行观察,于是,调用DispList成为必要。


  用于測试CreateList的測试函数写作:

int main()
{
    SqList *sq;
    ElemType x[6]= {5,8,7,2,4,9};
    CreateList(sq, x, 6);
    DispList(sq);
    return 0;
}

   再例,測试初始化操作InitList时。能够与插入元素的操作ListInsert一起进行。

int main()
{
    SqList *sq;
    InitList(sq);
    ListInsert(sq, 1, 5);
    ListInsert(sq, 2, 3);
    ListInsert(sq, 1, 4);
    DispList(sq);
    return 0;
}

  接下来,能够更换測试函数,用于測试别的基本操作。

“宜小不宜大”的原则简化了工作的难度,可是可能须要编制多个測试函数,分多次执行,才干将多个測试函数測试完。

二、构建自己的“算法库”

  数据结构课程的主线就是各种逻辑结构,在不同的存储结构下,基本运算的实现与复杂度分析。

这些基本数据结构的定义,以及各种基本运算的实现,能够构成“算法库”。建立起来的算法库。能够作为即将要解决这个问题时,所依托的“基础设施”。其实,在project中用到的系统库,或第三方的算法库,也是将通用的模块集中起来而形成的。


  为有效地组织。程序将用多文件的方式组织。当中的头文件和提供自定函数实现的文件。组成的就是算法库。
  对于线性表的顺序存储,算法库的头文件能够定义为:
list.h

#ifndef LIST_H_INCLUDED
#define LIST_H_INCLUDED

#define MaxSize 50
typedef int ElemType;
typedef struct
{
    ElemType data[MaxSize];
    int length;
} SqList;
void CreateList(SqList *&L, ElemType a[], int n);//用数组创建线性表
void InitList(SqList *&L);//初始化线性表InitList(L)
void DestroyList(SqList *&L);//销毁线性表DestroyList(L)
bool ListEmpty(SqList *L);//判定是否为空表ListEmpty(L)
int ListLength(SqList *L);//求线性表的长度ListLength(L)
void DispList(SqList *L);//输出线性表DispList(L)
bool GetElem(SqList *L,int i,ElemType &e);//求某个数据元素值GetElem(L,i,e)
int LocateElem(SqList *L, ElemType e);//按元素值查找LocateElem(L,e)
bool ListInsert(SqList *&L,int i,ElemType e);//插入数据元素ListInsert(L,i,e)
bool ListDelete(SqList *&L,int i,ElemType &e);//删除数据元素ListDelete(L,i,e)#endif // LIST_H_INCLUDED
#endif

  配套地,在list.cpp中实现这些基本操作。


  
list.cpp

#include <stdio.h>
#include <malloc.h>
#include "list.h"

//用数组创建线性表
void CreateList(SqList *&L, ElemType a[], int n)
{
    int i;
    L=(SqList *)malloc(sizeof(SqList));
    for (i=0; i<n; i++)
        L->data[i]=a[i];
    L->length=n;
}

//初始化线性表InitList(L)
void InitList(SqList *&L)   //引用型指针
{
    L=(SqList *)malloc(sizeof(SqList));
    //分配存放线性表的空间
    L->length=0;
}

//销毁线性表DestroyList(L)
void DestroyList(SqList *&L)
{
    free(L);
}

//判定是否为空表ListEmpty(L)
bool ListEmpty(SqList *L)
{
    return(L->length==0);
}

//求线性表的长度ListLength(L)
int ListLength(SqList *L)
{
    return(L->length);
}

//输出线性表DispList(L)
void DispList(SqList *L)
{
    int i;
    if (ListEmpty(L)) return;
    for (i=0; i<L->length; i++)
        printf("%d ",L->data[i]);
    printf("
");
}

//求某个数据元素值GetElem(L,i,e)
bool GetElem(SqList *L,int i,ElemType &e)
{
    if (i<1 || i>L->length)  return false;
    e=L->data[i-1];
    return true;
}

//按元素值查找LocateElem(L,e)
int LocateElem(SqList *L, ElemType e)
{
    int i=0;
    while (i<L->length && L->data[i]!=e) i++;
    if (i>=L->length)  return 0;
    else  return i+1;
}

//插入数据元素ListInsert(L,i,e)
bool ListInsert(SqList *&L,int i,ElemType e)
{
    int j;
    if (i<1 || i>L->length+1)
        return false;   //參数错误时返回false
    i--;            //将顺序表逻辑序号转化为物理序号
    for (j=L->length; j>i; j--) //将data[i..n]元素后移一个位置
        L->data[j]=L->data[j-1];
    L->data[i]=e;           //插入元素e
    L->length++;            //顺序表长度增1
    return true;            //成功插入返回true
}

//删除数据元素ListDelete(L,i,e)
bool ListDelete(SqList *&L,int i,ElemType &e)
{
    int j;
    if (i<1 || i>L->length)  //參数错误时返回false
        return false;
    i--;        //将顺序表逻辑序号转化为物理序号
    e=L->data[i];
    for (j=i; j<L->length-1; j++) //将data[i..n-1]元素前移
        L->data[j]=L->data[j+1];
    L->length--;              //顺序表长度减1
    return true;              //成功删除返回true
}

  大功告成。假设这些函数的实现还没有经过測试的话。编制main.cpp,在当中写測试函数完毕測试工作。
  比如。为測试CreateList。写
  
main.cpp

#include "list.h"
int main()
{
    SqList *sq;
    ElemType x[6]= {5,8,7,2,4,9};
    CreateList(sq, x, 6);
    DispList(sq);
    return 0;
}

三、应用数据结构求解问题

  要实现的内容必然是要基于特定的存储结构,可能利用各基本操作的组合就能够完毕。也可能须要在数据结构上,自己设计算法完毕。也不排除主要部分是专门设计的算法,但有些环节。也须要基本操作的支持。总之,能够看出。从解决这个问题的角度。须要综合运用知识了,同一时候,前面建的算法库,有了用武之地。
  比如:设顺序表有10个元素。其元素类型为整型。设计一个算法。以第一个元素为分界线。将全部小于它的元素移到该元素的前面。将全部大于它的元素移到该元素的后面。


  设计出的算法是:

void move1(SqList *&L)
{
    int i=0,j=L->length-1;
    ElemType pivot=L->data[0];
    ElemType tmp;
    while (i<j) 
    {
        while (i<j && L->data[j]>pivot)
            j--;    
        while (i<j && L->data[i]<=pivot)
            i++;    
        if (i<j)
        {
            tmp=L->data[i];
            L->data[i]=L->data[j];
            L->data[j]=tmp;
        }
    }
    tmp=L->data[0]; 
    L->data[0]=L->data[j];
    L->data[j]=tmp;
}

  为了将其作为一个程序,通过执行观察程序的执行过程,能够写出例如以下的main.cpp。能够看出。前面编制好的头文件list.h和list.cpp正在支持着这些工作。
  
main.cpp

#include "list.h"
void move1(SqList *&L)  //定义解决这个问题的算法
{
    int i=0,j=L->length-1;
    ElemType pivot=L->data[0];
    ElemType tmp;
    while (i<j)
    {
        while (i<j && L->data[j]>pivot)
            j--;
        while (i<j && L->data[i]<=pivot)
            i++;
        if (i<j)
        {
            tmp=L->data[i];
            L->data[i]=L->data[j];
            L->data[j]=tmp;
        }
    }
    tmp=L->data[0];
    L->data[0]=L->data[j];
    L->data[j]=tmp;
}
int main()   //在main函数中调用。保证程序能执行。解决这个问题
{
    SqList *sq;
    ElemType x[10]= {3, 8, 2, 7, 1, 5, 3, 4, 6, 0};
    CreateList(sq, x, 10);
    DispList(sq);
    move1(sq);
    DispList(sq);
    return 0;
}

程序执行结果:

3 8 2 7 1 5 3 4 6 0
1 0 2 3 3 5 7 4 6 8

四、执行程序,观察算法 法过程

  作为学习算法的过程,建议在须要的时候,通过单步执行的方式,观察程序的执行过程,以此来理解算法的执行细节。比如,下图是上面样例在单步执行过程中的一个截屏。
这里写图片描写叙述
  非常显然。这样的将执行过程“可视化”的方式,对于算法的学习而言,是非常有效的。

原文地址:https://www.cnblogs.com/tlnshuju/p/7372880.html