Meny
Är gratis
registrering
Hem  /  Utbildning/ Sök efter en delsträng i texten (sträng). Brute force-algoritm

Sök efter en delsträng i texten (sträng). Brute force-algoritm

Jag har ett matematiskt problem som jag löser genom att trial and error (jag tror att detta kallas brute force) och programmet fungerar bra när det finns flera parametrar, men när fler variabler/data läggs till tar det längre och längre tid att köra.

Mitt problem är att även om prototypen fungerar är den användbar för tusentals variabler och stora datamängder; så jag undrar om brute force-algoritmer kan skalas. Hur kan jag närma mig att skala den?

3 svar

Vanligtvis kan du kvantifiera hur väl en algoritm kommer att skala genom att använda stor slutledningsnotation för att analysera dess tillväxthastighet. När du säger att din algoritm fungerar under brute force är det oklart i vilken utsträckning den kommer att skalas. Om din brute-force-lösning fungerar genom att lista alla möjliga delmängder eller kombinationer av datamängden, kommer den nästan säkert inte att skalas (den kommer att ha asymptotisk komplexitet O (2 n) respektive O (n!). Om din brute force-lösning fungerar genom att hitta alla elementpar och testa dem, kan den skala tillräckligt bra (O (n 2)). Dock utan ytterligare information hur din algoritm fungerar är svårt att säga.

Per definition är brute force-algoritmer dumma. Du kommer att bli mycket bättre med en smartare algoritm (om du har en). En bättre algoritm kommer att minska det arbete som behöver göras, förhoppningsvis till den grad att du kan göra det utan att behöva "skala" över flera maskiner.

Oavsett algoritm kommer det en tid då den nödvändiga mängden data eller beräkningskraft så stor att du behöver använda något som Hadoop. Men generellt pratar vi verkligen om big data här. Du kan redan nu göra mycket med en dator.

Algoritmen för att lösa detta problem är stängd för processen som vi studerar för manuell matematisk division, såväl som för att konvertera från decimal till en annan bas, såsom oktal eller hexadecimal, förutom att i de två exemplen beaktas endast en kanonisk lösning.

För att säkerställa att rekursionen är komplett är det viktigt att beställa datasetet. För att vara effektiv och för att begränsa antalet rekursioner är det viktigt att börja med högre datavärden också.

Specifikt, här är en rekursiv Java-implementering för detta problem - med en kopia av koeff-resultatvektorn för varje rekursion som förväntat i teorin.

Importera java.util.Arrays; public class Solver (public static void main (String args) (int target_sum = 100; // förutsättning: sorterade värden!! int data = new int (5, 10, 20, 25, 40, 50); / / resultatvektor, init till 0 int coeff = new int; Arrays.fill (coeff, 0); partialSum (data.length - 1, target_sum, coeff, data);) private static void printResult (int coeff, int data) ( for ( int i = coeff.length - 1; i> = 0; i--) (if (coeff [i]> 0) (System.out.print (data [i] + "*" + coeff [i] + " ");)) System.out.println ();) privat statisk void partialSum (int k, int summa, int coeff, int data) (int x_k = data [k]; för (int c = summa / x_k) ; c > = 0; c--) (koeff [k] = c; if (c * x_k == summa) (printResult (koeff, data); fortsätt;) annars om (k> 0) (// kontextuellt resultat i parametrar, lokal till metodomfattning int newcoeff = Arrays.copyOf (coeff, coeff.length); partialSum (k - 1, summa - c * x_k, newcoeff, data); // för loop på "c" fortsätter med föregående koeff innehåll ))))

Men nu är den här koden i ett specialfall: det sista testet av värdet för varje koefficient är 0, så ingen kopia behövs.

Som en uppskattning av komplexiteten kan vi använda det maximala djupet för rekursiva anrop som data.length * min ((data)). Naturligtvis kommer det inte att skalas bra och stackspårningsminnet (JVM -Xss-alternativet) kommer att vara en begränsad faktor. Koden kan misslyckas med ett fel för stort set data.

För att undvika dessa nackdelar är "uppsägningsprocessen" till hjälp. Den består i att ersätta metodanropsstacken med en mjukvarustack för att lagra exekveringskontexten för senare bearbetning. Här är koden för det:

Importera java.util.Arrays; importera java.util.ArrayDeque; importera java.util.Queue; offentlig klass NonRekursiv (// förutsättning: sorterade värden!! privat statisk slutlig int-data = ny int (5, 10, 20, 25, 40, 50); // Kontext för att lagra mellanliggande beräkning eller en statisk lösningsklass Context ( int k; int summa; int coeff; Context (int k, int summa, int coeff) (this.k = k; this.sum = summa; this.coeff = coeff;)) privat statisk tomrum printResult (int coeff) ) ( för (int i = coeff.length - 1; i> = 0; i--) (if (coeff [i]> 0) (System.out.print (data [i] + "*" + coeff [ i] + "");)) System.out.println ();) public static void main (String args) (int target_sum = 100; // resultatvektor, init till 0 int coeff = new int; Arrays.fill ( coeff, 0); // kö med sammanhang för att bearbeta kö sammanhang = ny ArrayDeque (); // initial context contexts.add (ny kontext (data.length - 1, target_sum, coeff)); while (! contexts.isEmpty ()) (Context current = contexts.poll (); int x_k = data; for (int c = current.sum / x_k; c> = 0; c--) (current.coeff = c ; int newcoeff = Arrays.copyOf (current.coeff, current.coeff.length); if (c * x_k == current.sum) (printResult (newcoeff); fortsätt;) annars if (current.k> 0) (sammanhang .add (ny kontext (current.k - 1, current.sum - c * x_k, newcoeff));)))))

Ur min synvinkel är det svårt att vara mer effektiv i en enda trådexekvering - stackmekanismen kräver nu kopior av coeff-arrayen.

Brute Force-metoden

Fullständig sökning(eller brute force-metoden från engelska råstyrka) är en metod för att lösa problemet genom att räkna upp alla möjliga alternativ... Komplexiteten i den uttömmande sökningen beror på rummets dimension av alla möjliga lösningar uppgifter. Om lösningsutrymmet är mycket stort, kan uttömmande sökning inte ge resultat på flera år eller till och med århundraden.

Se vad "Brute Force Method" är i andra ordböcker:

    Full brute force (eller "brute force"-metoden) är en metod för att lösa ett problem genom att räkna upp alla möjliga alternativ. Komplexiteten i den uttömmande sökningen beror på dimensionen av utrymmet för alla möjliga lösningar på problemet. Om beslutet utrymme ... ... Wikipedia

    Sortering efter enkla byten, sortering efter en bubbla (eng. Bubble sort) är en enkel sorteringsalgoritm. För förståelse och implementering är denna algoritm den enklaste, men den är endast effektiv för små arrayer. Algoritmkomplexitet: O (n²). Algoritm ... ... Wikipedia

    Denna term har andra betydelser, se brute force. Full brute force (eller "brute force"-metoden) lösningsmetod matteproblem... Tillhör klassen av metoder för att hitta en lösning genom att uttömma alla typer av ... ... Wikipedia

    Omslaget till den första upplagan av broschyren "Bale's Documents ..."

    Snefru är en kryptografisk enkelriktad hashfunktion föreslagen av Ralph Merkle. (Själva namnet Snefru, som fortsätter traditionen med blockchiffrorna Khufu och Khafre, också utvecklat av Ralph Merkle, är namnet på den egyptiska ... ... Wikipedia

    Ett försök att bryta (dekryptera) data krypterad med ett blockchiffer. Alla större typer av attacker är tillämpliga på blockchiffer, men det finns vissa attacker som endast är specifika för blockchiffer. Innehåll 1 Typer av attacker 1.1 Allmänt ... Wikipedia

    Neurokryptografi är en gren av kryptografi som studerar tillämpningen av stokastiska algoritmer, i synnerhet, neurala nätverk, för kryptering och kryptoanalys. Innehåll 1 Definition 2 Tillämpning ... Wikipedia

    Den optimala reseförsäljarvägen genom de 15 största städerna i Tyskland. Den angivna rutten är den kortaste av alla möjliga vägar 43 589 145 600. Resande säljare problem (TSP) (Wikipedia

    Kryptografisk hashfunktion Namn N Hash Skapad 1990 Publicerad 1990 Hashstorlek 128 bitar Antal omgångar 12 eller 15 Hashtyp N Hash kryptografisk ... Wikipedia

    Problemet med resande säljare (resande säljare) är ett av de mest kända kombinatoriska optimeringsproblemen. Uppgiften är att hitta den mest lönsamma vägen som passerar angivna städer minst en gång med ... ... Wikipedia


Ris. 11.6. Procedur för att skapa listor Ris. 11.7. Grafisk bild stack Ris. 11.8. Exempel på binärt träd Ris. 11.9. Steget att konvertera ett träd till ett binärt

Sorteringsproblemet anges på följande sätt. Låt det finnas en matris med heltal eller riktiga nummer Det är nödvändigt att ordna om elementen i denna array så att de efter omarrangeringen ordnas i icke-minskande ordning: formeln "src =" http://hi-edu.ru/e-books/xbook691/files/178- 2.gif "border =" 0 " align = "absmiddle" alt = "(! LANG:Om siffrorna är parvis olika, så talar de om ordning i stigande eller fallande ordning. I det följande kommer vi att överväga problemet med icke-minskande beställning, eftersom andra problem löses på liknande sätt. Det finns många sorteringsalgoritmer, alla med sina egna hastighetsegenskaper. Låt oss överväga de enklaste algoritmerna för att öka hastigheten på deras arbete.

Sortera efter utbyten (bubbla)

Denna algoritm anses vara den enklaste och långsammaste. Sorteringssteget består av att gå från botten till toppen genom arrayen. I det här fallet är par av angränsande element synliga. Om elementen i ett par är i fel ordning, så byts de.

Efter den första passagen genom arrayen visar sig "toppen" (i början av arrayen) vara det "lättaste" (minimum) elementet, därav analogin med en bubbla som dyker upp (fig. 11.1)
). Nästa pass görs till det andra föremålet från toppen, så det näst största föremålet lyfts till rätt position, och så vidare.

Passningarna görs längs den ständigt minskande botten av arrayen tills det bara finns ett element kvar i den. Detta slutför sorteringen, eftersom sekvensen är i stigande ordning.

undertext "> Sortera efter val

Urvalssortering är något snabbare än bubbelsortering. Algoritmen är som följer: du måste hitta det element i arrayen som har det minsta värdet, ordna om det med det första elementet och sedan göra detsamma, med början från det andra elementet, etc. Således skapas en sorterad sekvens genom att det ena elementet efter det andra fästs i rätt ordning. På i-te steget välj det minsta av elementen a [i] ... a [n], och byt ut det med ett [i]. Stegsekvensen visas i fig. 11.2
.

Oavsett numret på det aktuella steget i, ordnas sekvensen a ... a [i]. Således, vid (n - 1):e steget, visar sig hela sekvensen förutom a [n] vara sorterad, och en [n] står på sista plats till höger: alla mindre element har redan gått till vänster.

undertext "> Sortera efter enkla infogningar

Arrayen skannas och varje nytt element a [i] infogas på lämplig plats i den redan beställda samlingen a, ..., a. Denna plats bestäms genom sekventiell jämförelse av a [i] med de ordnade elementen a, ..., a. Således "växer" en sorterad sekvens i början av arrayen.

Men i bubbel- eller urvalssortering kan man tydligt ange att i det i-te steget är elementen a ... a på rätt plats och kommer inte att flytta sig någon annanstans. Vid sortering genom enkla insättningar kan man hävda att sekvensen a ... a är ordnad. I det här fallet, under algoritmens gång, kommer alla nya element att infogas i den (se namnet på metoden).

Tänk på algoritmens åtgärder i det i-te steget. Som nämnts ovan är sekvensen vid denna punkt uppdelad i två delar: ett färdigt a ... a och ett oordnat a [i] ... a [n].

Vid nästa, i-te varje steg i algoritmen, tar vi ett [i] och infogar det på rätt plats i den färdiga delen av arrayen. Sökandet efter en lämplig plats för nästa element i inmatningssekvensen utförs genom successiva jämförelser med elementet framför det. Beroende på resultatet av jämförelsen förblir elementet antingen i sin nuvarande position (insättningen är klar), eller så byts de ut och processen upprepas (Figur 11.3)
).

Under insättningsprocessen "silar" vi alltså elementet X till början av arrayen, och stoppar om

  • ett element hittas som är mindre än X;
  • början av sekvensen nås.

Definiera "> med den linjära sökalgoritmen, när hela arrayen genomkorsas sekventiellt och det aktuella elementet i arrayen jämförs med det önskade. Vid matchning lagras indexet/indexen för det hittade elementet.

En sökuppgift kan dock ha många ytterligare villkor. Till exempel hitta minimum- och maximumelementet, söka efter en delsträng i en sträng, söka i en array som redan är sorterad, söka för att ta reda på om det finns nödvändigt element utan att ange index osv. Låt oss överväga några typiska sökuppgifter.

Sök efter en delsträng i texten (sträng). Brute Force Algorithm

Sökandet efter en delsträng i en sträng utförs enligt ett givet mönster, dvs. någon sekvens av tecken, vars längd inte överstiger längden originallinje... En söknings uppgift är att avgöra om en sträng innehåller ett givet mönster och att ange platsen (index) i strängen om en matchning hittas.

Brute-force-algoritmen är den enklaste och långsammaste och består i att kontrollera alla positioner i texten för sammanträffande med början av mönstret. Om början av mönstret stämmer överens, så jämförs nästa bokstav i mönstret och i texten, och så vidare tills den helt matchar mönstret eller skiljer sig från den i nästa bokstav.

undertext "> Boyer-Moore Algorithm

Den enklaste versionen av Boyer-Moore-algoritmen består av följande steg. Det första steget är att bygga en tabell med offset för det önskade provet. Sedan är början av raden och mönstret justerade och kontrollen börjar från det sista tecknet i mönstret. Om det sista tecknet i mönstret och dess motsvarande strängtecken när det överlagras inte stämmer överens, förskjuts mönstret i förhållande till strängen med den mängd som erhålls från offsettabellen, och jämförelsen utförs igen, med början med det sista tecknet i mönstret . Om tecknen stämmer överens jämförs mönstrets näst sista tecken, och så vidare. Om alla tecken i mönstret matchade de överlagrade tecknen i strängen, så hittades understrängen och sökningen är över. Om något (inte det sista) tecknet i mönstret inte matchar motsvarande tecken i strängen, flyttar vi mönstret ett tecken åt höger och börjar kontrollera igen från det sista tecknet. Hela algoritmen exekveras tills antingen en förekomst av det önskade mönstret hittas, eller tills slutet av strängen nås.

Mängden förskjutning vid missmatchning av det sista tecknet beräknas enligt regeln: förskjutningen av mönstret måste vara minimal, för att inte missa förekomsten av mönstret i raden. Om det givna strängtecknet förekommer i mönstret, skiftas mönstret så att strängtecknet matchar förekomsten längst till höger av det tecknet i mönstret. Om mönstret inte innehåller detta tecken alls, så förskjuts mönstret med en mängd lika med dess längd så att det första tecknet i mönstret överlagras på nästa tecken i strängen.

Mängden offset för varje tecken i mönstret beror bara på ordningen på tecknen i mönstret, så det är bekvämt att beräkna offseten i förväg och lagra dem som en endimensionell array, där varje tecken i alfabetet motsvarar en förskjutning i förhållande till det sista tecknet i mönstret. Låt oss förklara allt ovan på enkelt exempel... Låt det finnas en uppsättning av fem tecken: a, b, c, d, e och du måste hitta förekomsten av mönstret "abbad" i strängen "abeccacbadbabbad". Följande diagram illustrerar alla stadier av algoritmexekveringen:

formel "src =" http://hi-edu.ru/e-books/xbook691/files/ris-page184-1.gif "border =" 0 "align =" absmiddle "alt =" (! LANG:

Start av sökning. Det sista tecknet i mönstret matchar inte det överlagrade linjetecknet. Flytta provet åt höger med 5 positioner:

formel "src =" http://hi-edu.ru/e-books/xbook691/files/ris-page185.gif "border =" 0 "align =" absmiddle "alt =" (! LANG:

Det sista tecknet är återigen inte detsamma som strängtecknet. I enlighet med tabellen över förskjutningar flyttar vi provet med två positioner:

formel "src =" http://hi-edu.ru/e-books/xbook691/files/ris-page185-2.gif "border =" 0 "align =" absmiddle "alt =" (! LANG:

Nu, i enlighet med tabellen, flyttar vi provet med en position och får den nödvändiga förekomsten av provet:

formel "src =" http://hi-edu.ru/e-books/xbook691/files/ris-page185-4.gif "border =" 0 "align =" absmiddle "alt =" (! LANG:

formel "src =" http://hi-edu.ru/e-books/xbook691/files/ris-page186.gif "border =" 0 "align =" absmiddle "alt =" (! LANG:

Funktionen BMSearch returnerar positionen för det första tecknet i den första förekomsten av mönstret P i sträng S. Om sekvensen P i S inte hittas returnerar funktionen 0 (kom ihåg att i ObjectPascal börjar numreringen av tecknen i String kl. 1). StartPos-parametern låter dig ange den position i S-strängen där du vill starta sökningen. Detta kan vara användbart om du vill hitta alla förekomster av P i S. För att söka från början av strängen, sätt StartPos lika med 1. Om sökresultatet inte är noll, för att hitta nästa förekomst av P i S måste du ställa in StartPos lika med värdet "föregående resultat plus provlängd".

Binär (binär) sökning

Binär sökning används om arrayen där den utförs redan är beställd.

Variablerna Lb och Ub innehåller de vänstra respektive högra gränserna för arraysegmentet där det erforderliga elementet finns. Sökningen börjar alltid med att undersöka mittelementet i linjesegmentet. Om det önskade värdet är mindre än mittelementet, måste du gå till sökningen i den övre halvan av segmentet, där alla element är mindre än det som just har markerats. Med andra ord, Ub blir (M-1), hälften av den ursprungliga arrayen kontrolleras vid nästa iteration. Som ett resultat av varje kontroll halveras således sökområdet. Till exempel, om det finns 100 nummer i arrayen, efter den första iterationen minskas sökområdet till 50 nummer, efter det andra - till 25, efter det tredje till 13, efter det fjärde till 7, etc. gif "border =" 0 "align =" absmiddle "alt =" (! LANG:

Dynamiska datastrukturer är baserade på användning av pekare och användning av standardprocedurer och funktioner för allokering/frigöring av minne under programmets gång. De skiljer sig från statiska datastrukturer, som beskrivs i avsnitten om typer och data. När en statisk variabel beskrivs i ett program, när programmet kompileras, beroende på typen av variabel, Bagge... Det är dock inte möjligt att ändra storleken på det tilldelade minnet.

Till exempel om en array är specificerad

Var S: array av röding,

sedan tilldelas 10 byte RAM för det en gång i början av programexekveringen.

Dynamiska strukturer kännetecknas av inkonsekvens och oförutsägbarhet i storleken (antal element) av strukturen under dess bearbetning.

Eftersom elementen i en dynamisk struktur är belägna på oförutsägbara minnesadresser, kan adressen för ett element i en sådan struktur inte beräknas från adressen för det startande eller föregående elementet.

För att upprätta länkar mellan element i en dynamisk struktur, används pekare genom vilka explicita länkar mellan element upprättas. Denna representation av data i minnet kallas koherent. Ett dynamiskt strukturelement består av två fält:

  • ett informationsfält eller datafält, som innehåller de data för vilka strukturen skapas; i det allmänna fallet är informationsfältet i sig en integrerad struktur: en post, en vektor, en array, en annan dynamisk struktur, etc.;
  • bindande fält som innehåller en eller flera pekare som länkar givet element med andra strukturelement.

När en sammanhängande datarepresentation används för att lösa ett tillämpat problem, görs endast innehållet i informationsfältet "synligt" för slutanvändaren, och bindningsfältet används endast av programmeraren-utvecklaren.

Fördelar med en sammanhängande datarepresentation:

  • förmågan att tillhandahålla betydande variation av strukturer;
  • begränsa storleken på strukturen endast till den tillgängliga mängden maskinminne;
  • när du ändrar den logiska sekvensen av strukturelement, krävs det inte att data flyttas i minnet, utan bara för att korrigera pekare;
  • stor flexibilitet hos strukturen.

Samtidigt är en sammanhängande representation inte utan sina nackdelar, varav de viktigaste är:

  • ytterligare minne spenderas på fälten med buntar;
  • tillgång till delar av en sammanhängande struktur kan vara mindre tidseffektiv.
  • Den sista nackdelen är den allvarligaste, och det är just för den som tillämpligheten av den sammanhängande datarepresentationen är begränsad. Om vi ​​i den sammanhängande representationen av data (matriser) för att beräkna adressen för något element, i alla fall behövde elementnumret och informationen i strukturdeskriptorn, så för en koherent representation kan adressen för elementet inte beräknas från initiala data. En deskriptor av en ansluten struktur innehåller en eller flera pekare som låter dig ange strukturen, sedan utförs sökningen efter det nödvändiga elementet genom att följa kedjan av pekare från element till element. Därför används den koherenta representationen nästan aldrig i uppgifter där den logiska datastrukturen har formen av en vektor eller array - med åtkomst via elementnummer, utan används ofta i uppgifter där den logiska strukturen kräver annan initial åtkomstinformation (tabeller, listor) , träd, etc. .).

    Dynamiska strukturer som används i programmering inkluderar:

    • dynamiska arrayer (diskuteras i ämne 6);
    • linjära listor;
    • stack;
    • vända, dec;
    • träd.

    En lista är en ordnad uppsättning som består av ett variabelt antal element för vilka operationerna inkludering och exkludering är tillämpliga. En lista som återspeglar grannskapsförhållandet mellan element kallas linjär. Längden på listan är lika med antalet element den innehåller, en lista med noll längd sägs vara tom. Linjära länkade listor är de enklaste dynamiska datastrukturerna.

    Det är bekvämt att grafiskt avbilda länkar i listor med pilar, som det visades i avsnittet som beskriver pekare. Om ett element i listan inte är associerat med något annat, skrivs ett värde som inte pekar på något element (en nollpekare) i pekarfältet. En sådan hänvisning i Pascal betecknas med Noll, och i diagrammet betecknas den med en överstruken rektangel. Nedan är strukturen enbart länkad lista(fig. 11.4
    ), dvs. kommunikation går från en del av listan till en annan åt ett håll... Här är fält D ett informationsfält som innehåller data (vilken typ som helst i Pascal), fält N (NEXT) är en pekare till nästa post i listan.

    Varje lista måste ha följande pekare: Head - huvudet på listan, Cur - det aktuella elementet i listan, ibland i listor som är enbart länkade används även Tail-pekaren - listans svans (i tvålänkslistor är det alltid använd). Pekarfältet för det sista elementet i listan är tomt, det innehåller ett speciellt tecken Noll, som indikerar slutet av listan och betecknas med en överstruken ruta.

    En dubbellänkad linjär lista skiljer sig från en enkellänkad lista genom att det i varje nod i listan finns ytterligare en pekare B (Tillbaka) som hänvisar till det föregående elementet i listan (fig. 11.5)
    ).

    Datastrukturen som motsvarar en dubbellänkad linjär lista beskrivs i Pascal enligt följande:

    markör ">

  • skapa en lista;
  • lägga till ett nytt objekt i listan: längst upp i listan, längst ner i listan, efter det aktuella objektet i listan, före det aktuella objektet i listan;
  • ta bort ett objekt från listan;
  • genomgång av listan för att bearbeta den;
  • sök efter en nod i listan;
  • Den brute force-metoden är ett direkt tillvägagångssätt för att lösa

    uppgifter, vanligtvis baserade direkt på uppgiftens redogörelse och definitionerna av de begrepp den använder.

    En brute-force-algoritm för att lösa ett allmänt sökproblem kallas sekventiell sökning. Denna algoritm jämför helt enkelt elementen i den angivna listan med söknyckeln i sin tur tills ett element med det angivna nyckelvärdet hittas (lyckad sökning) eller hela listan kontrolleras men det nödvändiga elementet hittas inte (misslyckad sökning). En enkel ytterligare teknik används ofta: om du lägger till en söknyckel i slutet av listan, kommer sökningen nödvändigtvis att lyckas, därför kan du ta bort kontrollen av att listan är klar i varje iteration av algoritmen. Följande är pseudokoden för en sådan förbättrad version; det antas att indata är i form av en array.

    Om den ursprungliga listan är sorterad kan du dra nytta av ytterligare en förbättring: du kan sluta söka i en sådan lista så snart ett objekt påträffas som inte är mindre än söknyckeln. Sekventiell sökning ger en utmärkt illustration av brute-force-metoden, med dess karakteristiska styrkor (enkelhet) och svagheter (låg effektivitet).

    Det är ganska uppenbart att körtiden för denna algoritm kan skilja sig inom mycket vida gränser för samma lista av storlek n. I värsta fall, dvs. när det obligatoriska elementet inte finns i listan, eller när det obligatoriska elementet är det sista i listan, kommer algoritmen att utföra det största antalet nyckeljämförelseoperationer med alla n element i listan: C (n) = n.

    1.2. Rabins algoritm.

    Rabins algoritm är en modifiering av den linjära algoritmen, den är baserad på en mycket enkel idé:

    "Föreställ dig att i ett ord A med längden m letar vi efter ett mönster X med längden n. Låt oss klippa ut ett "fönster" av storlek n och flytta det längs inmatningsordet. Vi är intresserade av om ordet i "fönstret" matchar det givna mönstret. Det tar lång tid att jämföra bokstäver. Istället fixar vi någon numerisk funktion på ord med längden n, då kommer problemet att reduceras till att jämföra tal, vilket utan tvekan är snabbare. Om värdena för denna funktion på ordet i "fönstret" och på provet är olika, finns det ingen matchning. Endast om värdena är desamma är det nödvändigt att successivt kontrollera matchningen bokstav för bokstav."

    Denna algoritm utför en linjär passage längs en linje (n steg) och en linjär passage över hela texten (m steg), så den totala körtiden är O (n + m). Samtidigt tar vi inte hänsyn till tidskomplexiteten för att beräkna hashfunktionen, eftersom kärnan i algoritmen är att denna funktion ska vara så lätt att beräkna att dess funktion inte påverkar den övergripande driften av algoritmen.

    Rabins algoritm och den sekventiella sökalgoritmen är de minst arbetskrävande algoritmerna, så de är lämpliga att använda för att lösa en viss klass av problem. Dessa algoritmer är dock inte de mest optimala.

    1.3. Knuth - Morris - Pratt (kmp) algoritm.

    KMP-metoden använder förbearbetning av den önskade strängen, nämligen: en prefixfunktion skapas utifrån dess. I det här fallet används följande idé: om prefixet (alias suffix) för en sträng med längden i är längre än ett tecken, så är det också prefixet för en delsträng med längden i-1. Därför kontrollerar vi prefixet för föregående delsträng, om det inte matchar, då prefixet för dess prefix, etc. Om vi ​​fortsätter på detta sätt hittar vi det största nödvändiga prefixet. Nästa fråga som är värd att besvara är: varför är körtiden för proceduren linjär, eftersom den innehåller en kapslad loop? Jo, för det första sker tilldelningen av prefixfunktionen exakt m gånger, resten av tiden ändras variabeln k. Därför är den totala körtiden för programmet O (n + m), det vill säga linjär tid.

    och följande funktion: function show (pos, sökväg, w, h) (var canvas = document.getElementById ("canID"); // hämta canvasobjektet var ctx = canvas.getContext ("2d"); // it har egenskap - ritningskontext var x0 = 10, y0 = 10; // positionen för kartans övre vänstra hörn.width = w + 2 * x0; canvas.height = h + 2 * y0; // ändra storlek på duken (något större än wxh) ctx.beginPath (); // börja rita en polylinje ctx.moveTo (x0 + pos.x, y0 + pos.y) // gå till 0:e staden för (var i = 1; i An exempel på resultatet av ett funktionsanrop Betydelsen av ritkommandona ska framgå av deras namn och kommentarer i koden.Först ritas en sluten polylinje (en försäljares väg) Sedan - städernas cirklar och ovanpå dem - städernas nummer. använd klass dra, vilket förenklar detta arbete och samtidigt låter dig få bilder i svg-format.

    Timern avbryts

    Det är inte svårt att implementera en uttömmande algoritm för att hitta den kortaste vägen i en timer. För att hålla den bästa vägen i arrayen minWay, kommer vi att skriva en funktion för att kopiera värdena för arrayelement src in i en array des:

    Funktion kopia (des, src) (if (des.length! == src.length) des = new Array (src.length); för (var i = 0; i