1. 程式人生 > >線性表——順序表的實現與講解(C++描述)

線性表——順序表的實現與講解(C++描述)

線性表

引言

新生安排體檢,為了 便管理與統一資料,學校特地規定了排隊的方式,即按照學號排隊,誰在前誰在後,這都是規定好的,所以誰在誰不在,都是非常方便統計的,同學們就像被一條線(學號)聯絡起來了,這種組織資料(同學)的方式我們可以稱作線性表結構

定義

線性表:具有零個或多個(具有相同性質,屬於同一元素的)資料元素的有限序列

若將線性表記為 ( a<sub>0</sub> , a<sub>1</sub> ,a<sub>i -1</sub> a<sub>i</sub> ,a<sub>i +1</sub>

, ... , a<sub>n - 1</sub> , a<sub>n</sub> )

  • 注意:i 是任意數字,只為了說明相對位置,下標即其線上性表中的位置)

  • 前繼和後繼:由於前後元素之間存在的是順序關係,所以除了首尾元素外,每個元素均含有前驅後繼,簡單的理解就是前一個 元素和後一個元素

  • 空表:如果線性表中元素的個數 n 為線性表長度,那麼 n = 0 的時候,線性表為空

  • 首節點、尾節點: 上面表示中的 :a<sub>0</sub> 稱作首節點,a<sub>n</sub> 稱作尾節點

抽象資料型別

  • 資料型別:一組性質相同的值的集合及定義在此集合上的一些操作的總稱

  • 抽象資料型別:是指一個數學模型及定義在該模型上的一組操作

關於資料型別我們可以舉這樣一個例子

  • 例如:我們常常用到的 整數型 浮點型 資料 這些都是資料的總稱,所有符合其性質特徵的都可以用其對應資料型別來定義,例如 520是一個滿足整數特徵的資料,所以可以賦值給 一個int型的變數 int love = 520;

像這些一般的資料型別通常在程式語言的內部定義封裝,直接提供給使用者,供其呼叫進行運算,而抽象資料型別一般由使用者自己根據已有的資料型別進行定義

抽象資料型別和高階程式語言中的資料型別實際上是一個概念,但其含義要比普通的資料型別更加廣泛、抽象

為什麼說抽象呢?是因為它是我們使用者為了解決實際的問題,與描述顯示生活且現實生活中的實體所對應的一種資料型別,我可以定義其儲存的結構,也可以定義它所能夠,或者說需要進行的一些操作,例如在員工表中,新增或刪除員工資訊,這兩部分就組成了 “員工” 這個抽象的資料型別

大致流程就是:

  • A:一般使用者會編寫一個自定義資料型別作為基礎型別

  • B:其中一些抽象操作就可以定義為該型別的成員函式,然後實現這些函式

  • C:如果對外的介面在公有域中,就可以通過物件來呼叫這些操作了

  • 當然,我們在使用抽象資料型別的時候,我們更加註意資料本身的API描述,而不會關心資料的表示,這些都是實現該抽象資料型別的開發者應該考慮的事情

線性表分為兩種——順序儲存結構和鏈式儲存結構,我們先來學習第一種

順序儲存結構

什麼是順序儲存結構呢?

順序儲存結構:用一段地址連續的儲存單元依次儲存線性表的資料元素

怎麼理解這這種儲存方式呢?

例如在一個菜園子中,有一片空地,我們在其中找一小塊種蔬菜,因為土地不夠平整疏鬆所以我們需要耕地,同時將種子按照一定的順序種下去,這就是對錶的初始化

菜園子可以理解為記憶體空間,空地可以理解為可以使用的記憶體空間,我們通過種蔬菜種子的方式,將一定的記憶體空間所佔據,當然,這片空間中你所放置的資料元素都必須是相同型別的 也就是說都得是蔬菜種子,有時候有些種子被蟲子咬壞了,我們就需要移除一些種子,買來以後再在空出來的位置中選地方種好,這也就是增加和刪除數元素

地址計算方式

從定義中我們可以知道 這種儲存方式,儲存的資料是連續的,而且相同型別,所以每一個數據元素佔據的儲存空間是一致的,假設每個資料 佔據 L個儲存單元那麼我們可以的出這樣的結論公式

$$Loc(a_i) = Loc(a_1) + (i -1)*L$$

  • i 代表所求元素的下標
  • 也就是單位長度乘以對應的個數

線性表的抽象資料型別

#ifndef _LIST_H_
#define _LIST_H_
#include<iostream>
using namespace std;

class outOfRange{};
class badSize{};
template<class T>
class List {
public:
    // 清空線性表
	virtual void clear()=0;
    // 判空,表空返回true,非空返回false
	virtual bool empty()const=0;
    // 求線性表的長度
	virtual int size()const=0;
    // 線上性表中,位序為i[0..n]的位置插入元素value
	virtual void insert(int i,const T &value)=0;
    // 線上性表中,位序為i[0..n-1]的位置刪除元素
	virtual void remove(int i)=0;
    // 線上性表中,查詢值為value的元素第一次出現的位序
	virtual int search(const T&value)const=0;
    // 線上性表中,查詢位序為i的元素並返回其值
	virtual T visit(int i)const=0;
    // 遍歷線性表
	virtual void traverse()const=0;
    // 逆置線性表
	virtual void inverse()=0;					
	virtual ~List(){};
};

/*自定義異常處理類*/ 


class outOfRange :public exception {  //用於檢查範圍的有效性
public:
	const char* what() const throw() {
		return "ERROR! OUT OF RANGE.\n";
	}
};

class badSize :public exception {   //用於檢查長度的有效性
public:
	const char* what() const throw() {
		return "ERROR! BAD SIZE.\n";
	}
};

#endif

在上面線性表的抽象資料型別中,定義了一些常用的方法,我們可以在其中根據需要,增刪函式

有了這樣的抽象資料型別List 我們就可以寫出線性表其下的順序結構和鏈式結構表的定義寫出來

異常語句說明:如果new在呼叫分配器分配儲存空間的時候出現了錯誤(錯誤資訊被儲存了一下),就會catch到一個bad_alloc型別的異常,其中的what函式,就是提取這個錯誤的基本資訊的,就是一串文字,應該是const char*或者string

順序表——順序儲存結構的定義

#ifndef _SEQLIST_H_
#define _SEQLIST_H_
#include "List.h"
#include<iostream>
using namespace std;

//celemType為順序表儲存的元素型別
template <class elemType>
class seqList: public List<elemType> { 
private:
	// 利用陣列儲存資料元素
	elemType *data;
    // 當前順序表中儲存的元素個數
    int curLength;
    // 順序表的最大長度
    int maxSize;
    // 表滿時擴大表空間
    void resize();							
public:
	// 建構函式
    seqList(int initSize = 10);				
 	// 拷貝建構函式
	seqList(seqList & sl);
    // 解構函式
    ~seqList()  {delete [] data;}
    // 清空表,只需修改curLength
    void clear()  {curLength = 0;}
    // 判空
	bool empty()const{return curLength == 0;}
    // 返回順序表的當前儲存元素的個數
    int size() const  {return curLength;}
    // 在位置i上插入一個元素value,表的長度增1
    void insert(int i,const elemType &value);
    // 刪除位置i上的元素value,若刪除位置合法,表的長度減1 
    void remove(int i);
    // 查詢值為value的元素第一次出現的位序
    int search(const elemType &value) const ;
    // 訪問位序為i的元素值,“位序”0表示第一個元素,類似於陣列下標
    elemType visit(int i) const;			
    // 遍歷順序表
    void traverse() const;
    // 逆置順序表
	void inverse();							
	bool Union(seqList<elemType> &B);
};

順序表基本運算的實現

(一) 建構函式

在建構函式中,我們需要完成這個空順序表的初始化,即創建出一張空的順序表

template <class elemType>
seqList<elemType>::seqList(int initSize) { 

	if(initSize <= 0) throw badSize();
	maxSize = initSize;
	data = new elemType[maxSize];
	curLength = 0;
						
} 

在這裡我們注意區分 initSize 和 curLenght 這兩個變數

  • initSize :初始化 (指定) 陣列長度
    • 陣列長度是存放線性表的儲存空間的長度,一般來說這個值是固定的,但是為了滿足需要很多情況下,我們會選擇動態的分配陣列,即定義擴容機制,雖然很方便,但是確帶來了效率的損失,我們在擴容的函式中會再提到這一問題
  • curLenght:線性表長度,即資料元素的個數

(二) 拷貝建構函式

template <class elemType>
seqList<elemType>::seqList(seqList & sl) { 

	maxSize = sl.maxSize;
	curLength = sl.curLength;
	data = new elemType[maxSize];
	for(int i = 0; i < curLength; ++i)
		data[i] = sl.data[i];
	
}

(三) 插入

我們下面來談一個非常常用的操作——插入操作,接著用我們一開始的例子,學校安排體檢,大家自覺的按照學號順訊排好了隊伍,但是遲到的某個學生Z和認識前面隊伍中的C同學,過去想套近乎,插個隊,如果該同學同意了,這意味著原來C同學前面的人變成了Z,B同學後面的人也從C變成了Z同學,同時從所插入位置後面的所有同學都需要向後移動一個位置,後面的同學莫名其妙的就退後了一個位置

我們來想一下如何用程式碼實現它呢,並且有些什麼需要特別考慮到的事情呢?

  • 1、插入元素位置的合法以及有效性
    • 插入的有效範圍:[0,curLength] 說明:curLength:當前有效位置
  • 2、檢查是否表滿,表滿不能繼續新增,否則發生溢位錯誤
    • A:不執行操作,報錯退出 (為避免可以將陣列初始大小設定大一些)
    • B:動態擴容,擴大陣列容量 (下例採用)
  • 3、首尾節點的特殊插入情況考慮
  • 4、移動方向
    • 利用迴圈,從表尾開始逐次移動,如果從插入位置開始,會將後面的未移動元素覆蓋掉
template <class elemType>
void seqList<elemType>::insert(int i, const elemType &value) { 
	
	//合法的插入範圍為【0..curlength】
	if (i < 0 || i > curLength) throw outOfRange(); 
	//表滿,擴大陣列容量
	if (curLength == maxSize) resize();		    
	for (int j = curLength; j > i; j--)
		//下標在【curlength-1..i】範圍內的元素往後移動一步
		data[j] = data[j - 1];
    //將值為value的元素放入位序為i的位置
	data[i] = value;
    //表長增加
	++curLength;	

}

(四) 刪除

既然理解了插入操作,趁熱打鐵,先認識一下對應的刪除操作,這個操作是什麼流程呢?還是上面的例子,插隊後的同學被管理人員發現,不得不離開隊伍,這樣剛才被迫集體後移的那些同學就都又向前移動了一步,當然刪除位置的前後繼關係也發生了改變

與插入相同,它又有什麼注意之處呢?

  • 1、刪除元素位置的合法以及有效性

    • 刪除的有效範圍:[0,curLength - 1]
    • i < 0 || i > curLength- 1隱性的解決了判斷空表的問題
  • 2、移動方向

    • 利用迴圈,從刪除元素的位置後開始逐次前移
template <class elemType>
void seqList<elemType>::remove(int i) { 
	
	//合法的刪除範圍
	if(i < 0 || i > curLength- 1) throw outOfRange();  
	for(int j = i; j < curLength - 1; j++)
		data[j] = data[j+1];
	--curLength; 
}

(五) 擴容操作

還記得嗎,我們在建構函式中,定義了陣列的長度 seqList<elemType>::seqList(int initSize) { 程式碼內容}

同時我們將這個初始化的指定引數值做為了 陣列的長度

maxSize = initSize;

為什麼我們不直接指定建構函式中的引數為 maxSize呢?

從變數名可以看出這是為了說明初始值和最大值不是同一個資料,也可以說是為了擴容做準備,

為什麼要擴容呢?

陣列中存放著線性表,但是如果線性表的長度(資料元素的個數)達到了陣列長度會怎麼樣?很顯然我們已經沒有多餘的空間進行例如插入這種操作,也稱作表滿了,所以我們定義一個擴容的操作,當涉及到可能表滿的情況,就執行擴容操作

擴容是不是最好的方式?

雖然陣列看起來有一絲不太靈光,但是陣列確實也是儲存物件或者資料的有效方式,我們也推薦這種方式,但是由於其長度固定,導致它在很多時候會受到一些限制,就例如我們上面的表滿問題,那麼如何解決呢?方法之一就是我們設定初始值比實際值多一些,但是由於實際值往往會有一些波動,就會導致佔用過多的記憶體空間造成浪費,或者仍發生表滿問題,為了解決實際問題,很顯然還是擴容更加符合需要,但是代價就是一定的效率損失

陣列就是一個簡單的線性序列,這使得元素訪問非常快速。但是為這種速度所付出的代價是陣列物件的大小被固定,並且在其生命週期中不可改變

我們看一下擴容的基本原理你就知道原因了!

擴容思想:

由於陣列空間在記憶體中是必須連續的,因此,擴大陣列空間的操作需要重新申請一個規模更大的新陣列,將原有陣列的內容複製到新陣列中,釋放原有陣列空間,將新陣列作為線性表的儲存區

所以為了實現空間的自動分配,儘管我們還是會首選動態擴容的方式,但是這種彈性顯然需要一定的開銷

template <class elemType>
void seqList<elemType>::resize() { 

	elemType *p = data;
	maxSize *= 2;
	data = new elemType[maxSize];
	for(int i = 0; i < curLength; ++i)
		data[i] = p[i];
	delete[] p; 
 
}

(六) 按值查詢元素

順序查詢值為value的元素第一次出現的位置,只需要遍歷線性表中的每一個元素資料,依次與指定value值比較

  • 相同:返回值的位序
    • 注意查詢的有效範圍
  • 找不到或錯誤:返回 -1

template<class elemType>
int seqList<elemType>::search(const elemType & value) const
{

	for(int i = 0; i < curLength; i++)
		if(value == data[i])return i;
	return - 1;

}

(七) 按位置(下標)查詢元素

這個就真的很簡單了,直接返回結果即可

template<class elemType>
elemType seqList<elemType>::visit(int i) const {

	return data[i];                                                                   
	
}

(八) 遍歷元素

遍歷是什麼意思呢?遍歷其實就是每一個元素都訪問一次,從頭到尾過一遍,所以我們就可以利用遍歷實現查詢,或者輸出等功能,如果表是空表,就輸出資訊提示,並且注意遍歷的有效範圍是[0,最後一個元素 - 1]

template<class elemType>
void seqList<elemType>::traverse()const {

	if (empty())
		cout << "is empty" << endl;	
	else {
		cout << "output element:\n";
		//依次訪問順序表中的所有元素
		for (int i = 0; i < curLength; i++)	
			cout << data[i] << " ";
		cout << endl;
	}
					
}

(九) 逆置運算

逆置運算顧名思義 ,就是將線性表中的資料顛倒一下,也就是說首元素和尾元素調換位置,然後就是第二個元素和倒數第二個元素調換,接著向中間以對為單位繼續調換,也可以稱作收尾對稱交換,需要注意的就是迴圈的次數僅僅是線性表長度的一半而已

template<class elemType>
void seqList<elemType>::inverse() {
	
	elemType tem;
	for(int i = 0; i < curLength/2; i++) {
		//調換的具體方式,可以設定一箇中間值
		tem = data[i];
		//對稱的兩個資料
		data[i] = data[curLength - i -1];
		data[curLength - i -1] = tem;
	}
		
}

(十) 合併順序表

現在給出兩個線性表,表A和表B,其中的元素均為正序儲存,如何可以合併兩個表,放於A表中,但是表中的元素仍然保證正序儲存

演算法思想:我們分別設定三個指標,分別代表了A B C,C 代表新表,我們分別讓三個指標指向三個表的末尾,將A表和B表的尾元素進行比較,然後將大的移入新A表中,然後將大的元素所線上性表的指標和新表的指標,前移一位 ,這樣A和B表繼續比較元素大小,重複操作,直到一方表空,將還有剩餘的那個表的剩餘元素移入新A表中

template<class elemType>
bool seqList<elemType>::Union(seqList<elemType> &B) {	

	int m, n, k, i, j;	
    //當前物件為線性表A
    //m,n分別為線性表A和B的長度
	m = this->curLength;						  
	n = B.curLength;
    //k為結果線性表的工作指標(下標)新A表中
	k = n + m - 1;	
    //i,j分別為線性表A和B的工作指標(下標)
	i = m - 1, j = n - 1;
    //判斷表A空間是否足夠大,不夠則擴容
	if (m + n > this->maxSize)					  
		resize();
    //合併順序表,直到一個表為空
	while (i >= 0 && j >= 0)					  
		if (data[i] >= B.data[j])
			data[k--] = data[i--];
		//預設當前物件,this指標可省略
		else data[k--] = B.data[j--];			  
	//將表B中的剩餘元素複製到表A中
	while (j >= 0)								  
		data[k--] = B.data[j--];
	//修改表A長度
	curLength = m + n;							  
	return true;
	 
}

順序表的優缺點

優點:

  1. 邏輯與物理順序一致,順序表能夠按照下標直接快速的存取元素
  2. 無須為了表示表中元素之間的邏輯關係而增加額外的儲存空間

缺點:

  1. 線性表長度需要初始定義,常常難以確定儲存空間的容量,所以只能以降低效率的代價使用擴容機制

  2. 插入和刪除操作需要移動大量的元素,效率較低

時間複雜度證明

讀取:

還記的這個公式嗎?

$$Loc(a_i) = Loc(a_1) + (i -1)*L$$

通過這個公式我們可以在任何時候計算出線性表中任意位置的地址,並且對於計算機所使用的時間都是相同的,即一個常數,這也就意味著,它的時間複雜度為 O(1)

插入和刪除:

我們以插入為例子

  • 首先最好的情況是這樣的,元素在末尾的位置插入,這樣無論該元素進行什麼操作,均不會對其他元素產生什麼影響,所以它的時間複雜度為 O(1)

  • 那麼最壞的情況又是這樣的,元素正好插入到第一個位置上,這就意味著後面的所有元素全部需要移動一個位置,所以時間複雜度為 O(n)

  • 平均的情況呢,由於在每一個位置插入的概率都是相同的,而插入越靠前移動的元素越多,所以平均情況就與中間那個值的一定次數相等,為 (n - 1) / 2 ,平均時間複雜度還是 O(n)

總結:

讀取資料的時候,它的時間複雜度為 O(1),插入和刪除資料的時候,它的時間複雜度為 O(n),所以線性表中的順序表更加適合處理一些元素個數比較穩定,查詢讀取多的問題

結尾:

如果文章中有什麼不足,或者錯誤的地方,歡迎大家留言分享想法,感謝朋友們的支援!

如果能幫到你的話,那就來關注我吧!如果您更喜歡微信文章的閱讀方式,可以關注我的公眾號

在這裡的我們素不相識,卻都在為了自己的夢而努力 ❤

一個堅持推送原創開發技術文章的公眾號:理想二旬不止

相關推薦

雙鏈的基本實現講解C++描述

雙鏈表 雙鏈表的意義 單鏈表相對於順序表,確實在某些場景下解決了一些重要的問題,例如在需要插入或者刪除大量元素的時候,它並不需要

線性——順序實現講解C++描述

線性表 引言 新生安排體檢,為了 便管理與統一資料,學校特地規定了排隊的方式,即按照學號排隊,誰在前誰在後,這都是規定好的,所以誰

資料結構演算法C語言 | 線性順序儲存、鏈式儲存

   線性表是最常用最簡單的線性結構    線性結構具有以下基本特徵: 線性結構是一個數據元素的有序(次序)集(處理元素有限)。若該集合非空,則 1)必存在唯一的一個“第一元素”; 2)必存在唯一的一個“最後元素”; 3)除第一元素之外,其餘每個元素均有唯一的前

資料結構——單鏈實現及操作c語言

#include <stdio.h> #include <stdlib.h> #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define INFEASIBLE -1 #d

鄰接的建立輸出C語言

鄰接表是圖的常用儲存結構之一,它很好的解決了鄰接矩陣佔用空間較大的問題。 鄰接表用到了兩個結構體,一個是頂點表,包括點的序號和連線此起點的第一條邊。一個是邊表,包括連線此邊的終點和對應之前起點的下一條邊。 初始化鄰接表時,先將定點表賦值,並把指標指向NULL。再將輸入的資料

求兩個單調不減單鏈的交集和並集C語言

一、思路: 構造struct node* Link(struct node *P,struct node *Q,int sign)函式,當sign=1時,返回P,Q的並集,當sign=0時,返回P,Q的交集,求交併的思路為: ①對P,Q分別賦予兩個指標p和q,初始時分別指向P,Q的頭結點

單鏈的相關操作和測試C語言

“single-LinkList.h” 標頭檔案 #ifndef _SINGLE_LL #define _SINGLE_LL #include<stdlib.h> #include<stdio.h> #define flag -100

Octavia 的實現分析OpenStack Rocky

目錄 文章目錄 目錄 Octavia 基本物件概念 基本使用流程 軟體架構 服務程序清單 程式碼結構 loadbalancer 建立流程分析 network_tasks.A

資料結構演算法Java描述-15、稀疏矩陣以及稀疏矩陣的三元組實現

一、稀疏矩陣 對一個m×n的矩陣,設s為矩陣元素個數的總和,有s=m*n,設t為矩陣中非零元素個數的總和,滿足t<<s的矩陣稱作稀疏矩陣。符號“<<”讀作小於小於。簡單說,稀疏矩陣就是非零元素個數遠遠小於元素個數的矩陣。相對於稀疏矩陣來說,一個不稀疏的矩陣也稱作稠密矩陣。

資料結構演算法C語言 | 二叉排序樹

二叉排序樹的定義—— 二叉排序樹 ( Binary Sort Tree) 或者為空;或者是具有如下特性的二叉樹: (1)若根的左子樹不空,則左子樹上所有結點的關鍵字均小於根結點的關鍵字; (2)若

線性篩莫比烏斯函式C++版

#include <bits/stdc++.h> using namespace std; const int N=1e7+50; //同時篩出素數和莫比烏斯函式 int p[N],miu[N]; bool check[N]; int pre[N]; void init(){

資料結構演算法Java描述-20、圖、圖的鄰接矩陣、有向圖的廣度優先遍歷深度優先遍歷

一、圖的基本概念圖:是由結點集合及結點間的關係集合組成的一種資料結構。結點和邊:圖中的頂點稱作結點,圖中的第i個結點記做vi。有向圖: 在有向圖中,結點對<x ,y>是有序的,結點對<x,y>稱為從結點x到結點y的一條有向邊,因此,<x,y>與<y,x>是兩條不同的邊。有向圖

通過兩個佇列實現一個棧C語言

stackBy2Queue.h檔案 #pragma once #define max_size 1000 typedef char DataType; typedef struct Queue { DataType data[max_size

兩個佇列實現一個棧C語言

本題的思路是先建立queue1和queue2,入棧時直接向queue1裡入佇列,出棧時需要先從queue1中出佇列的同時把數依次進入queue2,直到queue1中到最後一個數為止,然後將queue1中的數出佇列,再將queue2中的數倒回queue1,這樣就實

陣列(一維和二維指標C語言

文章目錄 讀者,你好! 如果你精通C,希望能得到你的斧正;如果你是初學者,希望能對你有所幫助。 加粗的是一些我認為比較重要的內容。 #一、指向一維陣列的指標 ##1、使指標指向陣列首地址的方法 int

利用opencv實現人臉檢測C++版

小編所有的帖子都是基於unbuntu系統的,當然稍作修改同樣試用於windows的,經過小編的絞盡腦汁,把剛剛發的那篇python 實現人臉和眼睛的檢測的程式用C++ 實現了,當然,也參考了不少大神的部落格,下面我們就一起來看看: Linux系統下安裝open

表示數值的字串C++描述

請實現一個函式用來判斷字串是否表示數值(包括整數和小數)。例如,字串"+100","5e2","-123","3.1416"和"-1E-16"都表示數值。 但是"12e","1a3.14","1.2.3

尋找最短迷宮路徑/電路佈線問題C++描述

基本思想和尋找迷宮路徑一致 https://mp.csdn.net/postedit/81980772 只是本文中找的是最短的路徑,而這一思想也經常用於電路佈線。主要方法是深度優先搜尋和回溯法。 基本的方法是從起始點開始,對其 上,右,下,左四個方向的位置進行距離標定,如果

常用的數論演算法C++描述

網上找到的是PASCAL的……於是自己轉成C++來寫一遍…… 1.求兩數的最小公倍數和最大公約數 //(輾轉相除法/歐幾里德演算法)//求兩數的最大公約數int gcd(int a,int b)...{    if(b==0)        return a;    els

工廠方法模式C++描述

工廠方法模式是對簡單工廠模式的改進。首先看看簡單工廠模式的缺點。 軟體是之所以區別於程式,是因為它可以被人們使用,並能間接創造效益。需求是軟體開發的核心,忽視使用者的需求,軟體本身就沒有存在的價值。 假如Nokia又新開發了一款新手機N99,對於採用簡單工廠模式設計的系統,