數據結構學習1——順序表(C語言描述)


數據結構本人主要學習嚴蔚敏老師的《數據結構(C語言版)》,本人根據自己的需要學習了書中的算法並將其代碼實現還加了自己的一些學習心得體會,現將學習歷程記錄下來以便日后需要時參考。主要是學的東西一多,這些當時掌握了的東西長久不用又會忘,而且自己的思路都是寶貴的財富啊,棄之可惜,所以記錄下來需要時隨時看看,免得又拿着一本書從頭開始還要到處找代碼。

線性表是最常用最簡單的一種數據結構,一個線性表是n個數據元素的有限序列線性結構的順序表示指的是用一組地址連續的存儲單元一次存儲線性表的數據元素,以元素在計算機內"物理位置相鄰"來表示線性表中數據元素之間的邏輯關系。

數據結構的學習就從這最簡單線性表中最簡單的順序表示開始。

相關概念:直接前驅元素,直接后驅元素,空表,位序等因為很簡單在此不予詳細介紹。

只要確定了存儲線性表的起始位置,線性表中任一數據元素都可以隨機存取,所以線性表的順序存儲結構是一種隨機存取的存儲結構由於高級程序設計語言(這里主要用指C)中的數組類型也有隨即存取的特性,因此通常用數組來描述數據結構中的順序存儲結構

學習步驟如下:順序表構造——順序表初始化——插入元素——刪除元素——元素比較——兩個順序表比較。

1.順序表構造

順序表構造前進行如下宏定義和變量替換,方便代碼的理解:

#define TRUE 1
#define FALSE 0
#define OK 1
#define ok 1
#define ERROR 0
#define error 0
#define INFEASIBLE -1

#define LIST_INIT_SIZE 100;
#define LISTINCREMENT 10;

typedef int ElemType;
typedef int Status;

采用結構體構造一個順序表,定義順序表的地址、長度、存儲容量的表示,代碼如下:

typedef struct{
    ElemType *elem;   //定義了順序表中元素類型的數組指針,指向順序表存儲空間的基址
    int length;       //順序表的長度(也即元素個數)
    int listsize;     //當前分配給順序表的存儲容量
}SqList;

 

2.順序表的初始化

接下來對該順序表進行初始化,為順序表分配一個預定義大小的數組空間,並將當前順序表長度設為0,如果元素個數大於分配的存儲容量則再對容量進行擴充(初始化時不擴充,順序表使用中按需要進行容量擴充)。代碼如下:

Status InitList(SqList *L)
{
    (*L).elem=(ElemType*)malloc(100*sizeof(ElemType));
    //不知什么問題不能用LIST_INIT_SIZE,必須用100,下面的realloc函數也是一樣?
    if((*L).elem==NULL)
    {
        exit(OVERFLOW);
    }
    (*L).length=0;
    (*L).listsize=LIST_INIT_SIZE;
    return ok;
}

malloc函數的使用:

函數原型:extern void *malloc(unsigned int num_bytes)

函數作用:向系統申請分配指定size個字節的內存空間。返回類型是 void* 類型。void* 表示未確定類型的指針。C,C++規定,void* 類型可以強制轉換為任何其它類型的指針

頭文件:VC中利用malloc函數時要加malloc.h或stdlib.h

返回值:如果分配成功則返回指向被分配內存的指針,否則返回空指針NULL。當內存不再使用時,應使用free()函數將內存塊釋放。

代碼中(*L).elem=(ElemType*)malloc(100*sizeof(ElemType));這句話返回值類型被強制轉換為ElemType*,因為返回值指向被配分內存指針(即順序表*L的elem元素)。分配內存大小為100個ElemType所占字節

為了調試方便,創建一個順序表並初始化,給其指定一定長度並向表中各元素賦值,代碼如下:

SqList* L1=new SqList();
InitList(L1);  //測試InitList函數
(*L1).length=10;
for(int j=0;j<(*L1).length;j++)
{
	(*L1).elem[j]=j;     
}

注意:定義了指針變量后,一定要將其初始化。一個不指向任何地址空間的指針是十分危險的!上面定義了一個SqList結構體指針變量並對其分配了內存空間。

為方便測試、簡化代碼量,寫一個輸出函數以供調用,用於輸出順序表L的個元素。代碼如下:

void Output_L(SqList *L)
{
	for(int i=0;i<(*L).length;i++)
		cout<<(*L).elem[i]<<" ";
}

可以調用Output_L函數看下順序表L1的輸出情況:Output_L(L1);   顯示結果如下:

image

初始化的這個順序表L1在本文后面進行測試時會大量用到。

 

3.順序表中元素的插入

功能:在順序表第i個位置前加入新的元素e。

思路第i個位置加入元素e,那么原來在第i個位置的元素就要移到i+1個位置中,依次向后推。移完后順序表長度+1。關鍵是如何移動的問題,如果從先插人e再移動,則插入后第i個位置的值是e,原先第i個位置的值就不知道了,因為存儲空間被e占用了,因此移動前還需要用個地址保存原先位置上的值。這樣比較麻煩,我們采用高低址向低地址移動,先將順序表最后一個位置(假設為p)的值傳入(P+1)中,這樣位置p就空出來了,將p-1的值放入p中,以此類推,最后第i個位置也空出來了,將e放入其中,這樣就完成了順序表的插入工作。

因為要插入元素,所以首先判斷插入的位置是否正確,再判斷存儲空間夠不夠,不夠就增加內存分配。

注意:這里我采用的是插入指針e中的值到順序表的第i個位置,因為ListInsert這個函數還要用到后面的一些功能中,我在學習過程中調試過很多次,有的函數參數用指針,有的函數參數沒用指針,在進行函數調用時很容易出錯,所以呢這里統一使用指針,以免函數調用時參數出現各種不匹配改又不好改。代碼如下:

Status ListInsert(SqList (*L),int i,ElemType *e)
{
    ElemType *newbase,*p,*q;
    if(i<1||i>(*L).length+1)
    {
        return ERROR;
    }
    if((*L).length>=(*L).listsize)
    {
        newbase=(ElemType *)realloc((*L).elem,((*L).listsize+10)*sizeof(ElemType));
        //不能用LISTINCREMENT,必須用10,下面一行就能用,為甚么?和realloc這個函數有關系嗎
        (*L).elem=newbase;
       (*L).listsize=(*L).listsize+LISTINCREMENT;
    }
    q=&((*L).elem[i-1]);
    for(p=&((*L).elem[(*L).length-1]);p>=q;p--)
    {
        *(p+1)=*p;
    }
    *q=*e;
    (*L).length++;
    return ok;
}

需要注意的是for循環中的循環終止條件。

realloc函數的使用:

函數原型:extern void *realloc(void *mem_address, unsigned int newsize)

參數意義:void*表示返回指針的數據類型,可以強制轉換為其他類型(這點和malloc是一樣的);函數第一個參數表示要改變內存大小的指針名,即要改變的是哪個指針指向的內存;第二個參數表示要分配的新的大小,新的大小一定要大於原來的大小不然會導致數據丟失。

頭文件:#include <stdlib.h> 有些編譯器需要#include <alloc.h>

功能:先按照newsize指定的大小分配空間,將原有數據從頭到尾拷貝到新分配的內存區域,而后釋放原來mem_address所指內存區域,同時返回新分配的內存區域的首地址。即重新分配存儲器塊的地址。

返回值:如果重新分配成功則返回指向被分配內存的指針,否則返回空指針NULL。

注意:這里原始內存中的數據還是保持不變的。當內存不再使用時,應使用free()函數將內存塊釋放

下面對ListInsert這個函數進行測試,假設將指針LI2中的值插入順序表L1中第5個位置,代碼如下:

ElemType LI1=8;
ElemType *LI2;
LI2=&LI1;
ListInsert(L1,5,LI2);   
cout<<"測試ListInsert函數:"<<endl;
Output_L(L1);
cout<<endl;

調試結果顯示如下:

image

 

4.順序表中元素的刪除

功能:將順序表的第i個位置的元素參數,並將該元素存到指針e中

思路刪除第i個位置的元素后,后面所有的元素所在的位置都要向前移一位。與元素添加后移位的方法相反,元素刪除后我們采取從地址低的位置向地址高的位置移動。因為是刪除元素,所以就不必判斷存儲空間夠不夠了,但刪除的位置是否靠譜還是必須要判斷的。不要忘了最后順序表的長度要-1。

代碼如下:

Status ListDelete(SqList *L,int i,ElemType *e)  
{
	ElemType *p,*q;
	if(i<0||i>=(*L).length)
	{
		return error;
	}
	q=&((*L).elem[i-1]);   //q為被刪除元素的位置
	*e=*q;
	p=&((*L).elem[(*L).length-1]);  //p指向順序表最后一個元素位置的地址
	for(q;q<p;q++)
	{
		*q=*(q+1);
	}
	(*L).length--;
	return ok;
}

需要注意的是for循環中的循環終止條件,可以和元素插入算法相比較下,有什么差別

對ListDelete函數進行測試,假設刪除的是順序表中L1第4個位置的元素,並將元素存儲到指針變量e中,代碼如下:

ElemType *e=new ElemType();
cout<<"測試ListDelete函數:"<<endl;
ListDelete(L1,4,e);
Output_L(L1);
cout<<endl;
cout<<"e="<<*e<<endl;

調試結果顯示如下:

image

 

5.順序表中元素比較

功能:在順序線性表中查找第一個與指針e中的值滿足Compare()關系的元素的位置,返回該位置,沒有就返回0。

首先,先定義一下Compare()這個函數,就假設這個關系是相等關系吧,利用Compare這個函數實現,如果兩個指針中的值相等就返回true,不相等就返回false。代碼如下:

bool Compare(ElemType* e1,ElemType* e2)    //參數要就都用指針表示,以免函數間相互調用時參數出現不匹配問題
{
	if(*e1==*e2)
		return true;
	else 
		return false;
}

根據這個關系,要找出順序表中的元素,明顯的思路就是一個個元素進行對比了,看是否滿足Compare()關系。這里需要注意的一點是元素的位置和數組元素的表示,第i個位置的元素是(*L).elem[i-1]代碼如下:

int LocateElem(SqList *L,ElemType *e)
{
	int i=1;       //i為順序表中的位置
	ElemType *p;   //p指向順序表中位序為i的元素
	p=(*L).elem;     //取數組首元素地址
	while(i<=(*L).length&&(!Compare(p,e)))
	{
		i++;
		p++;
	}
	if(i<=(*L).length)
		return i;       //返回滿足條件的元素所在位置i
	else 
		return 0;
}

對LocateElem函數進行測試,代碼如下:

ElemType LE1=4;
ElemType *LE2;
LE2=&LE1;
cout<<"測試LocateElem函數:"<<endl;
int a=LocateElem(L1,LE2);
cout<<LE1<<"元素的位置:"<<a<<endl;

調試顯示結果如下:

image

 

6.兩個順序表之間的運算

功能:將存在線性表Lb中而不存在La中的數據元素插入到線性表La中,是在La中元素后面依次插入,沒有按什么順序插入。

思路這算是順序表應用的小綜合,首先要判斷線性表Lb中的各元素在線性表La中是否存在,要用到LocateElem函數,還要對元素進行插入,要用到ListInsert函數。因為這兩個函數前面都理解的很透徹了,再來完成這個算法想必會比較簡單。

算法涉及到取元素,定義一個函數GetElem()將線性表L中第i個元素存入指針e中,代碼如下:

void GetElem(SqList *L,int i,ElemType *e)
{
	if(i>0&&i<=(*L).length)
		*e=(*L).elem[i-1];
}

利用這個函數和前面的兩個函數完成該算法就很簡單了,直接放代碼,如下:

void Collect(SqList *La,SqList *Lb)
{
	ElemType *e=new ElemType();    //局部指針變量分配了內存空間,用完后要注意釋放內存空間
	int La_len=(*La).length;
	int Lb_len=(*Lb).length;
	for(int i=1;i<=Lb_len;i++)
	{
		GetElem(Lb,i,e);
		if(!LocateElem(La,e))
		{
			ListInsert(La,La_len+1,e);   //注意這個時候La的長度並沒有加1
			La_len++;
		}
	}
	delete(e);
}

對該算法進行測試,測試時要初始化另外一個順序表L2,代碼如下:

cout<<"測試Connect函數:"<<endl;
SqList *L2=new SqList();  
InitList(L2);   //定義了SqList變量並初始化后,在賦值之前千萬別忘了使用該函數構造空一個線性表
(*L2).length=10;
for(int j=0;j<(*L2).length;j++)
{
	(*L2).elem[j]=j*2;
}
cout<<"線性表L2:"<<endl;
Output_L(L2);
cout<<endl;
Collect(L1,L2);
cout<<"執行Connect函數后的線性表L1:"<<endl;
Output_L(L1);

調試顯示結果如下:

image

 

注意:程序最后不要忘了將用new分配了內存空間的指針變量用delete釋放內存空間。順序表如果不用了,申請的內存空間要free掉。

順序表的操作基本上是這些,還有其他的操作用到時再學吧,順序表就到此為止。接下來是線性表的鏈式表示和實現了,加油!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM