Föreläsningsanteckningar om listor (PDF).

Datorer och
programmering TDB2:
Lista - en dynamisk
datastruktur
Vad är en lista?
En lista är en sekventiell struktur oftast av
likadana saker.
Listor denieras i C++ ofta med hjälp av
klasser, men representationen (dvs vilka
dataattribut klassen har) kan ske på olika sätt:
ett index.
•
•
•
&
$'
Statisk array. Längden bestäms under
kompileringen.
Dynamisk array. Längden bestäms
under exekveringen.
Dynamisk, tänjbar array. Kan växa och
krympa under exekveringen.
Länkad lista: Varje element åtkoms med
hjälp av pekare från t ex föregående
element. Kan växa och krympa under
exekveringen.
∗
Delvis knyckta delar av Olle Eriksson
'
0-0
1
2
3
4
5
8
4
6
4
0
-2
maxList-1
3
Indexerad lista
8
4
0
6
4
-2
3
Länkad lista
&
1
Vad bör en lista klara av?
0
Figur 1: Länkad lista.
%
$
Vilka saker kan en lista göra? Nästan alltid vill
man t ex kunna lägga till ett element eller söka
om ett speciellt element nns i listan eller ej.
En enkel lista bör klara av
•
Tala om sin aktuella längd, dvs antalet
listelement.
•
Tala om sin maximala kapacitet, dvs max
antal element som listan kan ha.
•
Ta bort alla element, dvs göra sig själv till
tom lista.
•
Ta bort ett element ur listan.
Lägga till ett nytt element, t ex sist.
•
%&
•
•
2
$
Indexerad lista: Varje element åtkoms med
∗
Eva Pärt-Enander
'
Indikera att ett element redan nns i listan
eller ej.
Indikera om listan är tom eller ej.
3
%
$'
'
Olika sorters listor
Array-baserad
implementation av lista
Ordnad lista: eller sorterad lista är en lista
Array-baserade listor representeras av tre delar:
i vilken elementen ligger sorterade i
stigande eller fallande ordning.
Ex. Telefonkatalogen, olika kartotek.
Stack: LIFO-struktur (Last In First Out), dvs
den som kommer sist blir behandlad först.
Ex. Olika former av travar, pappershögar
och liknande.
Kö: FIFO-struktur (First in First Out), dvs
den som kommer först blir behandlad först.
Ex. En lång rad konsumenter som hyser en
gemensam längtan att träa producenten av
en resurs.
&
'
4
Exempel på klassdeklaration för klassen
med statisk array:
SimpleList
const int maxList = 25;
class SimpleList
{
private:
int listItem[maxList];
int size;
public:
SimpleList();
void clear();
void addToEnd( int newItem );
void insert( int newItem );
void remove( int itemToGo );
int member( int anyItem );
int maximumCapacity();
&
int currentSize();
int empty();
void write();
};
6
$
• const int maxList:
en heltalskonstant
som indikerar maximala antalet tillåtna
element.
• int size:
ett heltal som anger antalet
element som nns vid ett visst tillfälle.
• int listItem[maxList]:
en array som kan
ha maxList antal heltalselement som mest.
Tillåtna indexvärden i arrayen listItem är
0, 1, ..., maxList.
Att ta bort alla element i listan motsvaras av
size = 0; Dock nns det dataelement kvar i
arrayen, men i och med att size nollställts, så
bildar de ingen lista.
%&
$'
5
Traversera en lista
%
$
Antag att vi vill komma åt alla element i
listan, då kan man iterera sig igenom listan
med en loop. Denna process kallas för
traversering. Exempel:
for ( int i = 0; i < size; i++ )
access( listItem[i] );
där access symboliserar en godtycklig funktion
med ett heltal som parameter eller någon sats
som involverar listItem[i], t ex nollställande
av element i konstruktorn eller utskrift i
metoden write().
De esta programerare använder for-loop
istället för while-loop då de traverserar en
lista.
Om listan är tom, dvs size är 0, så kommer
loopen ej att utföras.
%&
7
%
$'
'
Addera element till ordnad lista
Addera element till en lista
Processen att addera ett element till en lista
beror på vilken sorts lista man har. Enklast är
att lägga till element i slutet av en
array-baserad lista.
void SimpleList:: addToEnd( int newItem )
{
if ( size < maxList )
{
listItem[size] = newItem;
size++;
}
}
&
'
Antag sorterad i stigande ordning. Enklaste
sättet att bibehålla ordnade strukturen är att
stoppa in nya element på rätt plats direkt.
(Jämför instickssortering.)
Om detta görs i array-baserad lista, så måste
vissa element skiftas ett steg.
Algoritm: Starta sist
1. Starta med sista elementet i listan, dvs sätt
indexpekaren current = size-1. Om
detta element större än newItem så skifta
ett steg åt höger till plats size, eftersom
vi vet att den ändå måste ytta på sig.
%&
$'
2. Minska indexpekaren med 1. Fortsätt med
vänster del-array.
8
{
int current = size - 1;
// Aktuellt index.
// Leta efter rätt plats, skifta.
•
> newItem ) {
listItem[current + 1] = listItem[current];
current--;
Många andra listor kan ta bort element var
som helst, beroende på elementvärden mer
än på position i listan. Man måste först
söka i listan för att hitta elementet som
skall bort.
•
För en array-baserad lista, så medför
elementborttagande att vissa andra
element måste skiftas (i motsatt riktning
jämfört med att addera element till en
lista).
// Nytt tal skall in efter.
listItem[current + 1] = newItem;
// Öka listlängd.
Antagandet har gjorts att listan inte är full, dvs
.
&
size < maxList
10
I en stack lägger man till och tar bort
element i ena änden, men i en kö lägger
man till i ena änden, men tar bort från
andra änden.
•
}
}
%
$
Processen att ta bort ett element ur en lista
beror också på vilken sorts lista man har.
while ( current >=0 && listItem[current]
size++;
9
Ta bort ett element från en lista
void SimpleList:: insert( int newItem )
$
%&
11
%
$'
'
$
Sortering och sökning
Ordnade listor behöver inte sorteras de är
redan sorterade. Många listor är osorterade,
men sorteras om man så behöver.
Bra att känna till följande sorteringsalgoritmer:
void SimpleList:: remove( int itemToGo )
{
int psn = 0;
// Position för nästa element
// Sök efter elem. att ta bort
while ( psn < size &&
listItem[psn] != itemToGo )
psn++;
// Om elementet funnet.
if ( psn < size )
{
for ( int i = psn; i < size-1; i++ )
listItem[i] = listItem[i+1]; // Skifta.
size--;
// Minska listan.
&
'
12
Exempel på huvudprogram som använder
SimpleList:
Instickssortering.
•
Urvalssortering.
•
Bubbelsortering.
Bra att känna till följande sökningsalgoritmer:
}
}
•
•
Lineär sökning. (Oordnad datamängd).
•
Binär sökning. (Ordnad datamängd).
%&
$'
13
%
$
int main()
{
SimpleList list;
list.insert( 34 );
list.remove(55);
list.insert( 27 );
list.insert( 345 );
if ( list.empty() )
list.insert( 58 );
cout << "Tom lista!!!" << endl;
list.write();
return 0;
list.clear();
}
list.write();
ger följande körning:
SimpleList another;
another.insert( 55 );
another.insert( 78 );
another.insert( 15 );
&
list = another;
list.write();
list.remove(15);
list.remove(78);
14
Antal element är 4:
27
34
58
15
55
78
345
Antal element är 0.
Antal element är 3:
Tom lista!!!
%&
15
%
'
Metodimplementation för övriga
metoder/konstruktorer:
$'
$
SimpleList:: SimpleList()
: size(0)
{
for ( int i = 0; i < maxList; i++ )
listItem[i] = 0;
int SimpleList:: maximumCapacity()
{
return maxList;
}
}
void SimpleList:: clear()
{
int SimpleList:: currentSize()
{
size = 0;
return size;
}
}
int SimpleList:: member( int anyItem )
{
for ( int i = 0; i < size; i++ )
int SimpleList:: empty()
{
return (size == 0);
{
if ( listItem[i] == anyItem )
&
'
return 1;
// TRUE
}
return 0;
// FALSE
}
16
void SimpleList:: write()
{
cout << "Antal element är " << size;
if ( !empty() )
%&
$'
17
%
$
Avancerad listklass med dynamisk
tänjbar array
Man skulle i SimpleList kunna ha en
dynamisk array listItem istället för statisk:
private:
{
int *listItem;
cout << ":";
int maxList;
for ( int i = 0; i < size; i++ )
cout << '\t' << listItem[i];
}
else
cout << ".";
cout << endl;
}
&
// 1 om tom, annars 0.
}
18
int size;
I princip kan allt vara som förut, men man
allokerar och avallokerar minne i konstruktorer
respektive destruktorn. Dessutom byts const
int maxList = 25; i header-len mot att
användaren t ex ger värdet för attributet
maxList.
Vinsten är inte så stor om man inte gör arrayen
tänjbar...
%&
19
%
'
En tänjbar array upptar alltid precis så
mycket minne som behövs. Den kan växa eller
krympa om t ex:
•
•
•
användaren skickar ett meddelande om
detta. Meddelandet innehåller nya längden.
den tilldelas en annan tänjbar array. Dess
nya längd blir längden på det som
tilldelades.
man adderar eller tar bort element.
$'
Först en liten varning:
Kom ihåg att det här med pekare som attribut
inte är enkelt!
•
Pekare och arrayer är relaterade till
varandra, men de är inte samma sak!
•
Så fort man har med pekare att göra så
måste man vara försiktig, eftersom det lätt
blir konstiga fel annars.
T ex pekare som pekar fel, tappar bort vad
de pekar på eller konstiga kopieringar av
data som sker, etc.
Vi skall studera klassen ExtArray (extensible
array) lite närmare nu:
class ExtArray
{
private:
&
'
int *array;
// Dynamisk array.
int bufSize;
// Aktuella längden.
public:
...
20
Tilldelningsoperatorn (=):
•
då motsvarande klass inte innehåller
pekarattribut. Datavärde för datavärde
kopieras då över från objekt2 till objekt1.
•
%&
$'
Om vi har pekare, som i klassen ExtArray,
så innebär
objekt1 = objekt2;
att även pekarna kopieras direkt! Dvs
pekarna i objekt1 och objekt2 pekar ut
samma data (samma minnesutrymme),
så om man senare ändrar i objekt2:s array,
så sker samma ändring i objekt1:s array,
fast man troligen inte ville det!
S k grund kopiering (ej fullständig,
ickedjup, attributkopiering, shallow copy).
&
22
21
Frågor: Vad händer om:
man gör delete [] på ena pekarvärdet?
räckvidderna (scope) är olika stora för
Den inbyggda tilldelningsoperatorn
fungerar utmärkt för varianten
objekt1 = objekt2;
$
%
$
objekt1, objekt2?
•
Botemedel: Man skriver en egen
tilldelningsfunktion, t ex:
void ExtArray:: assign( const
ExtArray & a );
dvs tilldelningen:
objekt1.assign( objekt2 );
fungerar då rätt om man implementerar
assign så att den skapar en helt egen
kopia av arrayen genom att kopiera
element för element, s k djup kopiering
(fullständig, deep copy).
Om &-tecknet glöms så måste en egen
kopieringskonstruktor nnas, annars blir
det fel i allafall... const medför att
objekt2 ej kan förstöras/ändras i metoden.
%&
23
%
$'
'
$
Kopieringskonstruktorn
•
Alternativt botemedel: Skriv en
överlagrad tilldelningsoperator så att
•
Det nns en inbyggd kopieringskonstruktor
i C++ som automatiskt används vid vissa
tillfällen fast man inte tänker på det. Allt
fungerar bra så länge som man inte har
pekare bland attributen.
•
Kopieringskonstruktorn anropas då man
initierar objektvariabler, vid
ickereferensparameteröverföring till
funktioner eller när man returnerar
objektvärden från funktioner.
•
Både den inbyggda kopieringskonstruktorn
och tilldelningsoperatorn gör alltså grund
kopiering!
objekt1 = objekt2;
innebär djup kopiering.
Deklareras i stil med det här:
const ExtArray & ExtArray::
operator= ( const ExtArray & a )
:-| Inte helt trivialt med C++.... :-0
&
'
•
24
Botemedel: Man skriver en egen
%&
$'
Kod för hela klassen ExtArray + main +
körning:
kopieringskonstruktor som kopierar
elementvis (dvs djupt).
// Extensible array. Tänjbar array.
const int blank = 0;
class ExtArray
ExtArray( const ExtArray & a );
{
medför att t ex
private:
ExtArray arr2;
int *array;
// default.
ExtArray arr3 = ExtArray( 2, -99 ); //överlagra
ExtArray arr4(arr3);
// kopierings.
ExtArray arr5 = arr2;
// kopierings.
int bufSize;
public:
ExtArray();
ExtArray( int siz, int val );
~ExtArray();
fungerar!
ExtArray( const ExtArray & a );
Summering:
Om man har pekare som attribut, så bör man
alltså ha egendenierad
&
25
%
$
destruktor, kopieringskonstruktor,
tilldelningsoperator/metod.
void change(int i, int num);
int size();
%&
void resize( int n );
void assign( const ExtArray & a );
void write();
void addToEnd( int newItem );
};
26
27
%
'
$'
int main()
{
$
ExtArray arr1( 5, -1 ); // 5 element, -1:or
ExtArray arr2;
// 0 element.
ExtArray arr3 = ExtArray( 2, -99 );
ExtArray arr4(arr3);
// kopiering..
ExtArray arr5 = arr2;
// kopiering..
cout << "arr1: "; arr1.write();
cout << "arr2: "; arr2.write();
cout << "arr3: "; arr3.write();
cout << "arr1: "; arr1.write();
cout << "arr5: "; arr5.write();
cout << "arr2: "; arr2.write();
cout << "arr3: "; arr3.write();
cout << "Tilldela arr1 = arr3:" << endl;
//
arr1 = arr3;
// Kopierar ickedjupt!!!
arr1.assign( arr3 ); // OKAY.
cout << "arr1: "; arr1.write();
cout << "arr2: "; arr2.write();
cout << "arr3: "; arr3.write();
&
'
cout << "arr3:
%&
$'
arr3.change(0,11); arr3.change(1,22);
28
29
Körning ger:
Hello överlagrad 5
cout << "arr3: ändra längd dessutom" << endl;
arr3.resize(6);
cout << "arr2: "; arr2.write();
cout << "arr3: "; arr3.write();
cout << "Lägg till element 22, 33 på "
<<
Hello default 0
Hello överlagrad 2
Hello kopiering: ny längd: 2
cout << "arr1: "; arr1.write();
"slutet i arr2, arr3:" << endl;
arr2.addToEnd( 22 );
// Nytt element sist
arr3.addToEnd( 33 );
// Nytt element sist
cout << "arr1: "; arr1.write();
cout << "arr2: "; arr2.write();
cout << "arr3: "; arr3.write();
&
%
$
byt värden till 11 och 22..."
<< endl;
Hello kopiering: ny längd: 0
arr1: Antal element är 5:
-1
-1
-1
-1
-1
arr2: Antal element är 0.
arr3: Antal element är 2: -99
-99
arr4: Antal element är 2: -99
-99
arr5: Antal element är 0.
Tilldela arr1 = arr3:
Hello assign 2
arr1: Antal element är 2: -99
arr3: Antal element är 2: -99
arr3:
%&
-99
byt värden till 11 och 22...
arr1: Antal element är 2: -99
return 0;
-99
arr2: Antal element är 0.
-99
arr2: Antal element är 0.
}
arr3: Antal element är 2:
11
22
arr1: Antal element är 2: -99
-99
arr3: ändra längd dessutom
arr2: Antal element är 0.
30
31
%
'
$'
Implementation av metoder för dynamisk
tänjbar arrayklass:
$
ExtArray:: ExtArray()
: bufSize(0)
{
array = 0; //NULL-pekaren
cout << "Hello default " << bufSize << endl;
}
arr3: Antal element är 6:
11
22
0
0
0
ExtArray:: ExtArray( int siz, int val )
0
Lägg till element 22, 33 på slutet i arr2, arr3:
arr1: Antal element är 2: -99
arr2: Antal element är 1:
-99
: bufSize(siz)
{
if ( bufSize > 0 )
22
{
arr3: Antal element är 7:
11
22
0
0
0
0
array = new int[bufSize];
33
for ( int i = 0; i < bufSize; i++ )
array[i] = val;
}
else
&
'
array = 0; //NULL-pekare
}
}
32
33
void ExtArray:: resize( int n ) {
int *p = 0;
bufSize(a.bufSize)
if ( n > 0 )
{
// Om ny storlek ej är noll
{
cout << "Hello kopiering: ny längd: "
// Skapa array med n elem.
<< bufSize << endl;
// Fyll med element.
// Fyll resten med blanka .
if ( bufSize > 0 )
p = new int[n];
{
// Allokera nytt minne.
int i;
// Kopiera element för element.
for ( i = 0; i < n
array = new int[bufSize];
&& i < bufSize; i++ )
for ( int i = 0; i < bufSize; i++ )
p[i] = array[i];
array[i] = a.array[i];
}
for ( ; i < n; i++ )
else
p[i] = blank;
array = 0; //NULL
}
}
&
%
$
cout << "Hello överlagrad " << bufSize << endl;
ExtArray:: ExtArray( const ExtArray & a )
:
%&
$'
{
ExtArray:: ~ExtArray()
{
delete [] array;
%&
delete [] array; // Avallokera.
array = p;
// Sätt array.
bufSize = n;
// Sätt bufSize.
}
}
34
35
%
'
$'
void ExtArray:: assign( const ExtArray
& a )
$
{
int ExtArray:: size()
// Tilldela: aktuella objektet = a.
{
return bufSize;
bufSize = a.bufSize;
}
cout << "Hello assign " <<
bufSize << endl;
void ExtArray:: write()
if ( bufSize > 0 )
{
{
cout << "Antal element är " << bufSize;
// Frigör minne.
if ( bufSize > 0 )
// Allokera nytt minne.
{
// Kopiera elementen
cout << ":";
delete [] array;
array = new int[bufSize];
for ( int i = 0; i < bufSize; i++ )
cout << '\t' << array[i];
for ( int i = 0; i < bufSize; i++ )
}
array[i] = a.array[i];
else
}
cout << ".";
&
'
%&
$'
else
cout << endl;
array = 0; //NULL
}
}
36
37
Överkurs Pekarbaserad implementation
av lista
void ExtArray:: addToEnd( int newItem )
%
$
Vad är en länkad lista?
{
// Öka på arrayen med ett element till.
first
resize(bufSize+1); // Fixar även bufSize++;
// Tilldela sista elem., dvs plats bufSize-1:
array[bufSize-1] = newItem;
link
}
link
4
3
link
5
void ExtArray:: change(int i, int num)
{
Figur 2: Länkad lista.
if ( i < bufSize )
array[ i ] = num;
•
}
&
%&
•
38
Består av ett antal noder eller
listelement. Värdet kan vara av vilken
typ som helst (int, double, klasstyp,...)
Implementeras vanligen som objekt som
binds samman med pekare.
39
%
'
•
Varje nod har två komponenter: ett
värde samt en pekare till nästa
element i listan.
•
Har en början och ett slut.
•
Listans första element kallas listans
huvud. Det brukar pekas ut av en speciell
pekare. (Vi kallar den first).
•
Det sista elementet har en tom pekare,
dvs pekarvärde = 0 (NULL). Den markerar
att listan är slut.
•
Är enkelriktad. (Man kan ha
dubbellänkade listor också...)
•
Element kan tas bort och läggas till var
som helst utan att andra element behöver
yttas.
&
'
•
•
$'
Dynamisk växer fram vartefter den
behövs; kan krympa och växa med tiden.
Har aldrig någon bestämd maximal storlek.
40
•
När en nod behövs så skaar vi en ny med
hjälp av new och lägger in den i listan.
•
Om man inte behöver noden så kan den
återvinnas med delete. Då måste vi
förstås trassla loss den ur listan först
annars blir det inte så bra.
Varför ha en länkad lista?
•
Fördelar: Mer dynamiskt än en array.
Lätt att lägga till och ta bort saker.
Kan hållas sorterad utan att noder
behöver yttas.
Ingen fast storlek.
%&
$'
41
Vad bör en länkad lista klara av?
Nackdelar:
Svårare att hitta en viss nod eftersom de
inte numreras utan vi måste söka från
början.
Är en sekventiell struktur där vi bara kan
gå framåt. Det nns varianter där vi kan
backa också men det ändrar inte så mycket.
En nod blir större än motsvarande
arrayelement eftersom en pekare måste
nnas. Andelen pekare i noden beror ju på
hur resten ser ut. Om vi har en lista av
tecken kommer 80% av listan att vara
pekare, men om informationsdelen är större
så minskar problemet.
&
$
Vi har redan sett exempel på vilka operationer
man önskar göra på listor (implementerad med
en array). Samma saker vill vi nu kunna göra
med länkade listor.
En enkel lista bör klara av
•
Skapa tom lista.
•
Ta bort alla element, dvs göra sig själv till
tom lista.
•
Ta bort ett element ur listan.
•
Lägga till ett nytt element, t ex sist.
•
Indikera att ett element redan nns i listan
eller ej.
•
Indikera om listan är tom eller ej.
%&
...etc. Detaljer kommer i OOP/C++-kursen!
42
%
$
43
%